Skip to content

routeTypesクエリへの移行とモーダルUIブラッシュアップ#16

Merged
TinyKitten merged 6 commits intomainfrom
fix/search
Feb 25, 2026
Merged

routeTypesクエリへの移行とモーダルUIブラッシュアップ#16
TinyKitten merged 6 commits intomainfrom
fix/search

Conversation

@TinyKitten
Copy link
Copy Markdown
Member

@TinyKitten TinyKitten commented Feb 25, 2026

  • 経路検索をroutesからrouteTypesに変更し、駅データはlineGroupStationsでバッチ取得
  • RouteInfoModalのデザイン改善: セクション分け、停車条件アイコン追加、フッター固定
  • 各線の種別を会社単位でグルーピングし、trainTypeがnullの場合のフォールバック改善
  • 路線カラードットがflex-wrap対応で多路線でも崩れないように修正

Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com

Summary by CodeRabbit

  • 新機能

    • 経路情報モーダルに停車条件アイコンと会社別の路線表示を追加
  • スタイル

    • 駅・路線の色ドットを折り返し対応レイアウトに変更し表示安定化
    • モーダルのヘッダー・ボタン・各セクションを視覚的に整理
  • バグ修正

    • 駅表示にフォールバック名を導入し、読み込みや欠損時の表示を改善
    • 経路表示の判定・向き計算を改良し表示誤差を低減
    • 大量駅取得の処理を安定化し表示の一貫性を向上

- 経路検索をroutesからrouteTypesに変更し、駅データはlineGroupStationsでバッチ取得
- RouteInfoModalのデザイン改善: セクション分け、停車条件アイコン追加、フッター固定
- 各線の種別を会社単位でグルーピングし、trainTypeがnullの場合のフォールバック改善
- 路線カラードットがflex-wrap対応で多路線でも崩れないように修正

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Feb 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
trainlcdtransfer Ready Ready Preview, Comment Feb 25, 2026 1:20pm

Request Review

@TinyKitten TinyKitten self-assigned this Feb 25, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 25, 2026

📝 Walkthrough

Walkthrough

GraphQLクエリをROUTESからROUTE_TYPESに切替え、trainTypeのgroupIdごとにステーションをチャンク/バッチ取得するロジックを導入。UIは駅/路線ドット表示をflex-wrapへ変更し、表示名取得の抽象化と表示条件のdisplayNameベース化、RouteInfoModalにSVGアイコンとセクション化された詳細レイアウトを追加。

Changes

Cohort / File(s) Summary
ページ・UI更新
app/page.tsx
ドット表示を固定高さ→flex flex-wrap gap-1に変更。getStationDisplayName導入、表示ロジックをdisplayName基準へ置換。方向計算をindex比較へ変更、has-type-change判定のヌル除外を追加。URL組立にsgid導入。
モーダルUI強化
components/RouteInfoModal.tsx
複数のインラインSVG(Stop/Pass/Partial/Weekday/Holiday)を追加し、STOP_CONDITIONSにIconを含める。ヘッダー・停車駅一覧・線種表示をセクション化、スクロール・ボタンスタイルを再構成。
GraphQLクエリ追加/変更
graphql/queries.ts
ROUTESを削除しROUTE_TYPESを追加。buildLineGroupStationsQuery(groupIds: number[])を追加し、動的エイリアスで複数のlineGroupStationsを一括取得可能に。
フェッチロジック(バッチ化)
hooks/useFetchRoutes.ts
SWRキーをrouteTypesへ変更。ROUTE_TYPESで得たtrainTypesのgroupIdをCHUNK_SIZE=10で分割し、fetchStationsByChunksで並列バッチ取得、各trainTypeを{ id, stops: Station[] }へマッピングする新フローへ。
軽微な整形・型追加
hooks/useFetchStationsByGroupId.ts, types/stationapi.ts, utils/generateSWRKey.ts
RoutesResponseを削除しRouteTypeTrainType/RouteTypesResponseを追加。GraphQL注釈や関数シグネチャの整形変更(動作影響なしの箇所あり)。

Sequence Diagram(s)

sequenceDiagram
  participant Page as app/page.tsx
  participant Hook as useFetchRoutes
  participant Aggregator as Chunk Aggregator
  participant API as GraphQL API

  Page->>Hook: useFetchRoutes()
  Hook->>API: query ROUTE_TYPES
  API-->>Hook: trainTypes (id, groupId)
  Hook->>Aggregator: chunk groupIds (CHUNK_SIZE=10)
  Aggregator->>API: buildLineGroupStationsQuery (batched aliases)
  API-->>Aggregator: lineGroupStations per alias
  Aggregator-->>Hook: Map<groupId, Station[]>
  Hook-->>Page: [{id: trainTypeId, stops: Station[]}, ...]
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 ちいさな点がころりと並び、
クエリは分けて駅をつのり、
モーダルには絵がきらり、
レイアウトはふわり折り重なり、
ぴょんと新しい道を祝うよ ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR タイトルは、routeTypes クエリへの移行とモーダル UI のブラッシュアップという、変更セットの主要な内容を正確に反映しています。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/search

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
components/RouteInfoModal.tsx (1)

170-174: 閉じるボタンに type="button" を明示してください。

将来的にフォーム配下で再利用された場合の意図しない submit を防げます。

差分案
                 <button
+                  type="button"
                   onClick={onClose}
                   aria-label="閉じる"
                   className="p-1 rounded-full hover:bg-gray-100 transition-colors"
                 >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/RouteInfoModal.tsx` around lines 170 - 174, In the RouteInfoModal
component, the close button (the element with onClick={onClose} and
aria-label="閉じる") needs an explicit type attribute to avoid accidental form
submits when this component is used inside a form; add type="button" to that
button element so it does not default to "submit".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hooks/useFetchRoutes.ts`:
- Around line 55-58: The code currently only reads the first page's trainTypes
(const trainTypes = res.routeTypes.trainTypes) and returns early, ignoring
RouteTypesResponse.nextPageToken; update the logic in useFetchRoutes.ts to loop
(or recurse) fetching pages while nextPageToken is truthy, appending each page's
res.routeTypes.trainTypes to a single accumulator array (e.g.,
accumulatedTrainTypes) and only return after nextPageToken is empty; ensure you
pass the token into the GraphQL call (where the request uses nextPageToken) and
preserve existing behavior when there is no pagination.
- Around line 60-66: The current mapping in useFetchRoutes.ts assigns Route.id =
tt.groupId causing potential id collisions; update the returned Route objects to
preserve both identifiers (set id = tt.id and include groupId: tt.groupId) so
routes remain unique, then update consumers (app/page.tsx) to consistently
compare the correct identifier—either change stop.trainType?.groupId ===
Number(selectedRouteId) to stop.trainType?.id === Number(selectedRouteId) if you
choose to make selectedRouteId mean trainType.id, or keep comparing groupId
everywhere and use selectedRouteId as a groupId; ensure the Route shape (id and
groupId) and the meaning of selectedRouteId are unified across useFetchRoutes.ts
and app/page.tsx.

---

Nitpick comments:
In `@components/RouteInfoModal.tsx`:
- Around line 170-174: In the RouteInfoModal component, the close button (the
element with onClick={onClose} and aria-label="閉じる") needs an explicit type
attribute to avoid accidental form submits when this component is used inside a
form; add type="button" to that button element so it does not default to
"submit".

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0bde5c1 and 2031786.

📒 Files selected for processing (7)
  • app/page.tsx
  • components/RouteInfoModal.tsx
  • graphql/queries.ts
  • hooks/useFetchRoutes.ts
  • hooks/useFetchStationsByGroupId.ts
  • types/stationapi.ts
  • utils/generateSWRKey.ts

Comment thread hooks/useFetchRoutes.ts
Comment on lines +55 to +58
const trainTypes = res.routeTypes.trainTypes;
if (trainTypes.length === 0) {
return [];
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== nextPageToken の参照箇所確認 =="
rg -n -C2 'nextPageToken' hooks/useFetchRoutes.ts graphql/queries.ts types/stationapi.ts 2>/dev/null || echo "nextPageToken references: none found or files not present"

echo
echo "== routeTypes 取得後ロジック確認 =="
rg -n -C3 'routeTypes|trainTypes.length|return \[\]' hooks/useFetchRoutes.ts 2>/dev/null || echo "routeTypes patterns: none found"

echo
echo "== useFetchRoutes.ts ファイル全体確認 =="
cat -n hooks/useFetchRoutes.ts

Repository: TrainLCD/Go

Length of output: 3557


nextPageToken が未使用のまま、ページング結果の欠落リスク

RouteTypesResponsenextPageToken フィールドが定義されており、GraphQL クエリも返していますが、コード内で参照されていません。現在の実装は1回の API 呼び出しで終わり、ページング環境では最初のページのみが処理されます。nextPageToken が空になるまで繰り返し取得するループが必要です。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useFetchRoutes.ts` around lines 55 - 58, The code currently only reads
the first page's trainTypes (const trainTypes = res.routeTypes.trainTypes) and
returns early, ignoring RouteTypesResponse.nextPageToken; update the logic in
useFetchRoutes.ts to loop (or recurse) fetching pages while nextPageToken is
truthy, appending each page's res.routeTypes.trainTypes to a single accumulator
array (e.g., accumulatedTrainTypes) and only return after nextPageToken is
empty; ensure you pass the token into the GraphQL call (where the request uses
nextPageToken) and preserve existing behavior when there is no pagination.

Comment thread hooks/useFetchRoutes.ts
Comment on lines +60 to +66
const groupIds = trainTypes.map((tt) => tt.groupId);
const grouped = await fetchStationsByChunks(groupIds);

return trainTypes.map((tt) => ({
id: tt.groupId,
stops: grouped.get(tt.groupId) ?? [],
}));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the file structure and locate the target file
find . -name "useFetchRoutes.ts" -o -name "page.tsx" | head -20

Repository: TrainLCD/Go

Length of output: 95


🏁 Script executed:

# Read the target file to see the code at lines 60-66
wc -l hooks/useFetchRoutes.ts

Repository: TrainLCD/Go

Length of output: 81


🏁 Script executed:

# Get more context around the mentioned lines
sed -n '50,75p' hooks/useFetchRoutes.ts

Repository: TrainLCD/Go

Length of output: 559


🏁 Script executed:

# Check the Route type definition
rg -n "type Route|interface Route" --type ts --type tsx

Repository: TrainLCD/Go

Length of output: 82


🏁 Script executed:

# Search for how Route.id is used throughout the codebase
rg -n "\.id\s*==|\.id\s*===|selectedRouteId" --type ts --type tsx

Repository: TrainLCD/Go

Length of output: 82


🏁 Script executed:

# Search for Route type definition
rg -n "type Route\s*=|interface Route\s*{" -A 5

Repository: TrainLCD/Go

Length of output: 186


🏁 Script executed:

# Search for selectedRouteId usage
rg -n "selectedRouteId" -B 2 -A 2

Repository: TrainLCD/Go

Length of output: 1955


🏁 Script executed:

# Check app/page.tsx for the groupId comparison mentioned in review
head -100 app/page.tsx | tail -50

Repository: TrainLCD/Go

Length of output: 1679


🏁 Script executed:

# Look for how trainTypes is structured
rg -n "trainTypes|RouteTypesResponse" -B 2 -A 5 -C 1

Repository: TrainLCD/Go

Length of output: 1494


🏁 Script executed:

# Get full RouteTypeTrainType structure
rg -n "RouteTypeTrainType|interface RouteType" -A 10

Repository: TrainLCD/Go

Length of output: 445


🏁 Script executed:

# Get Route type definition
rg -n "^type Route\s*=|^export type Route\s*=|^export interface Route" -A 10

Repository: TrainLCD/Go

Length of output: 627


🏁 Script executed:

# Check graphql query to see what fields are selected
sed -n '220,240p' graphql/queries.ts

Repository: TrainLCD/Go

Length of output: 594


🏁 Script executed:

# Check the entire useFetchRoutes function to understand flow
cat -n hooks/useFetchRoutes.ts | head -70

Repository: TrainLCD/Go

Length of output: 2412


🏁 Script executed:

# Check if stops/Station has trainType property
rg -n "interface Station|type Station" -A 10

Repository: TrainLCD/Go

Length of output: 1699


🏁 Script executed:

# Verify the logic - check if multiple trainTypes can have same groupId
rg -n "trainType\?\.id|trainType\?.groupId" -B 2 -A 2

Repository: TrainLCD/Go

Length of output: 585


🏁 Script executed:

# Check if there's any documentation or test showing multiple trainTypes with same groupId
rg -n "groupId.*trainTypes|trainTypes.*groupId"

Repository: TrainLCD/Go

Length of output: 138


🏁 Script executed:

# Check what STATION_FRAGMENT includes to understand if trainType is in stops
rg -n "STATION_FRAGMENT|StationFields" -A 20

Repository: TrainLCD/Go

Length of output: 2749


🏁 Script executed:

# Verify the complete flow - check what properties Station/stops actually have when returned
rg -n "stop\." app/page.tsx | head -20

Repository: TrainLCD/Go

Length of output: 697


🏁 Script executed:

# Check if there's any type that extends Station with trainType
rg -n "trainType" types/stationapi.ts

Repository: TrainLCD/Go

Length of output: 152


🏁 Script executed:

# Check STATION_FIELDS to see what's included
sed -n '1,110p' graphql/queries.ts | tail -30

Repository: TrainLCD/Go

Length of output: 464


🏁 Script executed:

# Find TrainType definition to understand its structure
rg -n "type TrainType|interface TrainType" -A 5

Repository: TrainLCD/Go

Length of output: 801


🏁 Script executed:

# Check if trainType has id field
rg -n "trainType\?.id" app/page.tsx

Repository: TrainLCD/Go

Length of output: 37


🏁 Script executed:

# Check if there are multiple trainTypes with same groupId in real usage
# Look for patterns or comments that explain the trainType selection logic
rg -n "trainType|groupId" components/RouteInfoModal.tsx -B 2 -A 2 | head -40

Repository: TrainLCD/Go

Length of output: 1189


🏁 Script executed:

# Get full context of how selectedRouteId is being used end-to-end
cat -n app/page.tsx | sed -n '610,650p'

Repository: TrainLCD/Go

Length of output: 1484


🏁 Script executed:

# Check if there's a reason why trainType.id isn't used currently
git log --oneline hooks/useFetchRoutes.ts | head -5

Repository: TrainLCD/Go

Length of output: 95


Route.idgroupId を使うと識別子衝突が発生するリスクがあります。

trainTypesidgroupId を別で返しているため、複数の trainType が同じ groupId を持つ場合、id: tt.groupId では Route.id が衝突する可能性があります。ただし、提案されている修正には注意が必要です。

現在、app/page.tsx の行 641 と 666 では stop.trainType?.groupId === Number(selectedRouteId) で trainType を検索しており、修正後に id: tt.id を使用すると、このロジックが機能しなくなります。修正時には以下の対応が必要です:

  1. RoutegroupId を保持させるか、別の方法で groupId を渡す
  2. 検索ロジックを trainType.id 比較に更新する
  3. selectedRouteId の意味を統一する(trainType.id か groupId か明確にする)

現在のコードは groupId で station 取得と trainType 検索の両方を行っており、単に id に変更するだけでは不十分です。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useFetchRoutes.ts` around lines 60 - 66, The current mapping in
useFetchRoutes.ts assigns Route.id = tt.groupId causing potential id collisions;
update the returned Route objects to preserve both identifiers (set id = tt.id
and include groupId: tt.groupId) so routes remain unique, then update consumers
(app/page.tsx) to consistently compare the correct identifier—either change
stop.trainType?.groupId === Number(selectedRouteId) to stop.trainType?.id ===
Number(selectedRouteId) if you choose to make selectedRouteId mean trainType.id,
or keep comparing groupId everywhere and use selectedRouteId as a groupId;
ensure the Route shape (id and groupId) and the meaning of selectedRouteId are
unified across useFetchRoutes.ts and app/page.tsx.

TinyKitten and others added 2 commits February 25, 2026 21:59
useFetchStationsByGroupIdからデータが取得できない場合に
スケルトンが永続する問題を、routesデータからの
フォールバック取得で解決。不要なFragmentも除去。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
trainTypeが未設定(undefined)の停車駅を除外してから
種別の一意性を判定するよう変更。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
方向の0/1が逆だった問題を修正し、groupIdの参照元を
undefinedになり得るfromStation/toStationから
selectedFromStationId/selectedToStationIdに変更。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/page.tsx (1)

662-679: 駅名フォールバック処理は共通化して重複を減らしたいです。

fromStationDisplayNametoStationDisplayName はほぼ同一ロジックなので、ヘルパー化すると今後の修正漏れを防げます。

💡 リファクタ例
+const resolveStationDisplayName = (
+  stationName: string | undefined,
+  selectedGroupId: string,
+  routes: Route[] | undefined,
+) => {
+  if (stationName) return stationName;
+  const groupId = Number(selectedGroupId);
+  const stop = routes?.flatMap((r) => r.stops).find((s) => s.groupId === groupId);
+  return stop ? `${stop.name}駅` : undefined;
+};

-const fromStationDisplayName = useMemo(() => {
-  if (fromStation?.name) return fromStation.name;
-  const fromGroupId = Number(selectedFromStationId);
-  const stop = routes
-    ?.flatMap((r) => r.stops)
-    .find((s) => s.groupId === fromGroupId);
-  return stop ? `${stop.name}駅` : undefined;
-}, [fromStation?.name, routes, selectedFromStationId]);
+const fromStationDisplayName = useMemo(
+  () => resolveStationDisplayName(fromStation?.name, selectedFromStationId, routes),
+  [fromStation?.name, routes, selectedFromStationId],
+);

-const toStationDisplayName = useMemo(() => {
-  if (toStation?.name) return toStation.name;
-  const toGroupId = Number(selectedToStationId);
-  const stop = routes
-    ?.flatMap((r) => r.stops)
-    .find((s) => s.groupId === toGroupId);
-  return stop ? `${stop.name}駅` : undefined;
-}, [toStation?.name, routes, selectedToStationId]);
+const toStationDisplayName = useMemo(
+  () => resolveStationDisplayName(toStation?.name, selectedToStationId, routes),
+  [toStation?.name, routes, selectedToStationId],
+);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/page.tsx` around lines 662 - 679, Extract the duplicated fallback logic
in the useMemo blocks for fromStationDisplayName and toStationDisplayName into a
single helper (e.g., getStationDisplayName(selectedStationId, station, routes))
that: returns station.name if present, otherwise converts selectedStationId to
Number, finds the matching stop via
routes.flatMap(r=>r.stops).find(s=>s.groupId===groupId) and returns
`${stop.name}駅` or undefined; then replace both useMemo bodies to call this
helper and ensure each useMemo's dependency array still references the same
inputs (selectedFromStationId/selectedToStationId,
fromStation?.name/toStation?.name, routes).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/page.tsx`:
- Around line 789-800: The subtitle logic currently renders nothing when only
one of fromStationDisplayName or toStationDisplayName is available; update the
conditional rendering so the subtitle paragraph (the <p> that uses
fromStationDisplayName and toStationDisplayName) displays when at least one of
fromStationDisplayName or toStationDisplayName is truthy and params.get("mode")
!== "line", and adjust its content to show a fallback or only the available name
(e.g., show "Unknown" or omit the missing side with the existing separator) so
users see meaningful text when only one station is resolved; keep the Skeleton
rendering when neither name exists by leaving its condition as the case where
both are falsy.

---

Nitpick comments:
In `@app/page.tsx`:
- Around line 662-679: Extract the duplicated fallback logic in the useMemo
blocks for fromStationDisplayName and toStationDisplayName into a single helper
(e.g., getStationDisplayName(selectedStationId, station, routes)) that: returns
station.name if present, otherwise converts selectedStationId to Number, finds
the matching stop via routes.flatMap(r=>r.stops).find(s=>s.groupId===groupId)
and returns `${stop.name}駅` or undefined; then replace both useMemo bodies to
call this helper and ensure each useMemo's dependency array still references the
same inputs (selectedFromStationId/selectedToStationId,
fromStation?.name/toStation?.name, routes).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2031786 and b01acd6.

📒 Files selected for processing (1)
  • app/page.tsx

Comment thread app/page.tsx Outdated
重複したフォールバックロジックをgetStationDisplayNameに統合し、
片方の駅名のみ取得できた場合にもサブタイトルを表示するよう修正。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/page.tsx (2)

713-720: ⚠️ Potential issue | 🟠 Major

useCallback の依存配列に selectedFromStationIdselectedToStationId が不足しています

handleLaunchApp 内で fromIndex/toIndex の算出に selectedFromStationIdselectedToStationId を直接使用していますが、依存配列には含まれていません。これにより、これらの値が変化した際に古い closure が使われ、direction の計算が stale な値に基づいて行われます。

🛠 修正案
   }, [
     devMode,
     fromStation?.groupId,
     fromStop?.line?.id,
     route?.stops,
+    selectedFromStationId,
+    selectedToStationId,
     selectedRouteId,
     toStation?.groupId,
   ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/page.tsx` around lines 713 - 720, handleLaunchApp を定義している useCallback
の依存配列に selectedFromStationId と selectedToStationId が抜けているため、これらが変化しても古いクロージャが使われ
direction 計算が stale になります;handleLaunchApp 内で fromIndex/toIndex を算出する際に参照している
selectedFromStationId と selectedToStationId を useCallback
の依存配列に追加して、クロージャがこれらの値変更時に再作成されるようにしてください(関数名: handleLaunchApp、変数:
selectedFromStationId, selectedToStationId、場所: useCallback の依存配列)。

700-710: ⚠️ Potential issue | 🟠 Major

fromStation?.groupIdundefined の場合、深リンクに sgid=undefined が含まれます

fromStationsByGroupId の取得が完了していない・またはエラーになった場合、fromStationundefined となり、sgid=undefined の URL が生成されます。lineId && lineGroupId のガードは通過してしまうため、アプリ側で不正なパラメータを受け取ります。

🛠 修正案
-    if (lineId && lineGroupId) {
+    if (lineId && lineGroupId && fromStation?.groupId != null) {
       window.open(
         `${appScheme}?sgid=${fromStation?.groupId}&lid=${lineId}&lgid=${lineGroupId}&dir=${direction}`,
       );
       return;
     }

-    if (lineId) {
+    if (lineId && fromStation?.groupId != null) {
       window.open(
         `${appScheme}?sgid=${fromStation?.groupId}&lid=${lineId}&dir=${direction}`,
       );
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/page.tsx` around lines 700 - 710, The deep-link URL includes
fromStation?.groupId which can be undefined; update the window.open logic in
app/page.tsx (the block using lineId, lineGroupId, fromStation, appScheme,
direction) to guard against missing fromStation: compute a sgid segment only
when fromStation and fromStation.groupId are defined (e.g. const sgidSegment =
fromStation?.groupId ? `sgid=${fromStation.groupId}&` : ''), then build the URL
using sgidSegment so you never emit "sgid=undefined"; apply this change to both
branches (the lineId && lineGroupId branch and the lineId-only branch) and
ensure parameters are correctly concatenated/encoded before calling window.open.
♻️ Duplicate comments (1)
app/page.tsx (1)

792-805: 片側のみ駅名が解決できた場合のサブタイトル表示が改善されました

以前のレビューで指摘した「片側のみ解決時に空白になる問題」が修正されています。(fromStationDisplayName || toStationDisplayName) 条件と "..." フォールバックにより、一方のみ取得できた場合も適切に表示されます。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/page.tsx` around lines 792 - 805, The subtitle rendering should show a
Skeleton when neither fromStationDisplayName nor toStationDisplayName is
available and show a single-line subtitle using the available name(s) (with
"..." fallback) when at least one is present; update the JSX around
params.get("mode") checks so the Skeleton branch uses condition
(params.get("mode") !== "line" && !fromStationDisplayName &&
!toStationDisplayName) and the subtitle paragraph uses (params.get("mode") !==
"line" && (fromStationDisplayName || toStationDisplayName)), rendering
{fromStationDisplayName ?? "..."} &nbsp;-&nbsp; {toStationDisplayName ?? "..."}
in the <p> and keep the Skeleton component as shown to cover the
neither-resolved case.
🧹 Nitpick comments (1)
app/page.tsx (1)

263-266: TypeScript の型絞り込みに型述語を使用してください

.filter((id) => id != null) は実行時には正しく機能しますが、TypeScript 的には (number | null | undefined)[] の型が絞り込まれず、new Set(typeIds) に渡す配列の型が不正確になる可能性があります。

♻️ 修正案
-      const typeIds = targetRoute?.stops
-        .map((s) => s.trainType?.typeId)
-        .filter((id) => id != null);
+      const typeIds = targetRoute?.stops
+        .map((s) => s.trainType?.typeId)
+        .filter((id): id is number => id != null);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/page.tsx` around lines 263 - 266, The array typeIds isn't narrowed from
(number | null | undefined)[] for TypeScript; change the filter on
targetRoute?.stops.map(s => s.trainType?.typeId) to use a type predicate such as
.filter((id): id is number => id != null) so typeIds becomes number[] before
calling new Set(typeIds).size > 1; update the filter only (referencing typeIds,
targetRoute?.stops, trainType?.typeId) to perform proper compile-time narrowing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/page.tsx`:
- Around line 824-831: handleStationClick is currently a no-op (returns () =>
{}) but is passed into StationListBox as onTrainTypeClick when selectedLineId is
set, so tapping a station does nothing; implement handleStationClick to perform
the intended action (e.g., open the route/station info modal or navigate to
station details) by setting the relevant state and invoking the modal/display
function used elsewhere in the component (for example, setSelectedStation(...)
and setIsRouteModalOpen(true) or call the existing
showRouteModal/showStationDetails helper), and replace the noop with that
implementation so StationListBox.onTrainTypeClick triggers the UI action in
route (selectedLineId) mode.
- Around line 691-697: The calculation of direction using fromIndex and toIndex
is unsafe because Array.prototype.findIndex can return -1; update the logic
around fromIndex, toIndex and direction (computed from route?.stops,
selectedFromStationId, selectedToStationId) to guard against -1 values: first
check if either fromIndex or toIndex is -1 and handle that case explicitly
(e.g., bail out/return early, set direction to null/undefined, or choose a safe
default) so you don't compute a spurious direction and generate invalid deep
links; ensure all downstream code that uses direction handles the new sentinel
(null/undefined) case.
- Around line 662-672: getStationDisplayName may produce duplicated "駅" (e.g.,
"東京駅駅") because it blindly appends "駅" to stop.name; update
getStationDisplayName to check stop.name (from the found stop in
routes.flatMap(...).find(...)) and only append "駅" when stop.name does not
already end with "駅" (use a string suffix check on stop.name) so returned value
is either stop.name (if already endsWith "駅") or `${stop.name}駅`, otherwise fall
back to undefined.

---

Outside diff comments:
In `@app/page.tsx`:
- Around line 713-720: handleLaunchApp を定義している useCallback の依存配列に
selectedFromStationId と selectedToStationId が抜けているため、これらが変化しても古いクロージャが使われ
direction 計算が stale になります;handleLaunchApp 内で fromIndex/toIndex を算出する際に参照している
selectedFromStationId と selectedToStationId を useCallback
の依存配列に追加して、クロージャがこれらの値変更時に再作成されるようにしてください(関数名: handleLaunchApp、変数:
selectedFromStationId, selectedToStationId、場所: useCallback の依存配列)。
- Around line 700-710: The deep-link URL includes fromStation?.groupId which can
be undefined; update the window.open logic in app/page.tsx (the block using
lineId, lineGroupId, fromStation, appScheme, direction) to guard against missing
fromStation: compute a sgid segment only when fromStation and
fromStation.groupId are defined (e.g. const sgidSegment = fromStation?.groupId ?
`sgid=${fromStation.groupId}&` : ''), then build the URL using sgidSegment so
you never emit "sgid=undefined"; apply this change to both branches (the lineId
&& lineGroupId branch and the lineId-only branch) and ensure parameters are
correctly concatenated/encoded before calling window.open.

---

Duplicate comments:
In `@app/page.tsx`:
- Around line 792-805: The subtitle rendering should show a Skeleton when
neither fromStationDisplayName nor toStationDisplayName is available and show a
single-line subtitle using the available name(s) (with "..." fallback) when at
least one is present; update the JSX around params.get("mode") checks so the
Skeleton branch uses condition (params.get("mode") !== "line" &&
!fromStationDisplayName && !toStationDisplayName) and the subtitle paragraph
uses (params.get("mode") !== "line" && (fromStationDisplayName ||
toStationDisplayName)), rendering {fromStationDisplayName ?? "..."}
&nbsp;-&nbsp; {toStationDisplayName ?? "..."} in the <p> and keep the Skeleton
component as shown to cover the neither-resolved case.

---

Nitpick comments:
In `@app/page.tsx`:
- Around line 263-266: The array typeIds isn't narrowed from (number | null |
undefined)[] for TypeScript; change the filter on targetRoute?.stops.map(s =>
s.trainType?.typeId) to use a type predicate such as .filter((id): id is number
=> id != null) so typeIds becomes number[] before calling new Set(typeIds).size
> 1; update the filter only (referencing typeIds, targetRoute?.stops,
trainType?.typeId) to perform proper compile-time narrowing.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b01acd6 and af29c28.

📒 Files selected for processing (1)
  • app/page.tsx

Comment thread app/page.tsx
Comment thread app/page.tsx
Comment thread app/page.tsx
Comment on lines +824 to +831
{selectedLineId ? (
<StationListBox
isLoading={isStationsLoading}
stations={stationsByLineId}
onTrainTypeClick={handleStationClick}
error={fetchStationsError}
/>
) : (
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

handleStationClick が no-op のため、路線モードで駅をタップしても何も起きません

handleStationClick(Line 621)は () => {} を返すだけです。StationListBoxonTrainTypeClick に渡されており、路線モード(selectedLineId が存在するケース)で駅をクリックしても一切のアクションが発生しません。意図的な未実装であれば TODO コメントを残すことを推奨します。

未実装の handleStationClick の動作(ルート情報モーダルを開くなど)を実装する issue を作成しましょうか?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/page.tsx` around lines 824 - 831, handleStationClick is currently a no-op
(returns () => {}) but is passed into StationListBox as onTrainTypeClick when
selectedLineId is set, so tapping a station does nothing; implement
handleStationClick to perform the intended action (e.g., open the route/station
info modal or navigate to station details) by setting the relevant state and
invoking the modal/display function used elsewhere in the component (for
example, setSelectedStation(...) and setIsRouteModalOpen(true) or call the
existing showRouteModal/showStationDetails helper), and replace the noop with
that implementation so StationListBox.onTrainTypeClick triggers the UI action in
route (selectedLineId) mode.

- typeIds filterにtype predicateを追加
- getStationDisplayNameの"駅"重複付与を防止
- handleLaunchAppのfindIndex -1ガードとsgid undefinedガードを追加
- handleLaunchAppのuseCallback依存配列にselectedFrom/ToStationIdを追加

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
app/page.tsx (1)

829-835: ⚠️ Potential issue | 🟠 Major

路線モードのリストタップが無反応のままです。

Line 833 で onTrainTypeClickhandleStationClick を渡していますが、Line 621 の handleStationClick が no-op なので、路線モードで項目タップ時に何も起きません。結果画面の主要導線が機能していない状態です。

🛠 修正例(既存のルート選択導線に合わせる)
-  const handleStationClick = useCallback((stationId: number) => () => {}, []);
+  const handleStationClick = useCallback(
+    (routeId: number, typeId: number | undefined) => () => {
+      const targetRouteId = typeId ?? routeId;
+      setValue("selectedRouteId", targetRouteId.toString());
+      params.update({ rid: targetRouteId.toString() });
+      onRouteInfoOpenChange();
+    },
+    [onRouteInfoOpenChange, params, setValue],
+  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/page.tsx` around lines 829 - 835, The StationListBox is wired to
onTrainTypeClick with handleStationClick, but handleStationClick at line 621 is
a no-op so tapping items does nothing; fix by wiring the prop to the actual
route-selection handler (e.g., replace handleStationClick with the real handler
used elsewhere such as handleTrainTypeClick or the route selection function that
triggers navigation/result rendering), or implement handleStationClick to call
the existing route selection logic (e.g., invoke the same function that
navigates to the results or sets the selected route/station); update the
StationListBox prop assignment and remove/replace the noop implementation so
taps execute the intended flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@app/page.tsx`:
- Around line 829-835: The StationListBox is wired to onTrainTypeClick with
handleStationClick, but handleStationClick at line 621 is a no-op so tapping
items does nothing; fix by wiring the prop to the actual route-selection handler
(e.g., replace handleStationClick with the real handler used elsewhere such as
handleTrainTypeClick or the route selection function that triggers
navigation/result rendering), or implement handleStationClick to call the
existing route selection logic (e.g., invoke the same function that navigates to
the results or sets the selected route/station); update the StationListBox prop
assignment and remove/replace the noop implementation so taps execute the
intended flow.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af29c28 and 07ec247.

📒 Files selected for processing (1)
  • app/page.tsx

@TinyKitten
Copy link
Copy Markdown
Member Author

@coderabbitai

路線モードのリストタップが無反応のままです。

これはこのPRの問題ではない

@TinyKitten TinyKitten merged commit 8025282 into main Feb 25, 2026
3 checks passed
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 25, 2026

Tip

For best results, initiate chat on the files or code changes.

@TinyKitten 承知しました。ご説明ありがとうございます。路線モードのリストタップの問題は、このPRの変更とは関係ないことを理解しました。

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