Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ookup Median grade may have zero votes and therefore no segment, causing the indicator line to be invisible. Compute the angle from the cumulative distribution so zero-vote grades are handled correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The chart always starts at the top so the 50th percentile always falls at the bottom of the ring. Remove getGradeAngle and draw the line with hardcoded coordinates instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Using userSpaceOnUse with CSS custom property stop-color failed to render. Switch to objectBoundingBox so the gradient spans the segment itself, and hardcode the D6 color value (#432414). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- max-w-xs → max-w-md for a larger chart - Add votedGrade prop; prefix matching segment label with ✅ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- OUTER_RADIUS 90→120, INNER_RADIUS 55→70 (ring width 35→50) - CX/CY 130→160/155, viewBox 260×275→320×310 - max-w-md→max-w-lg - Scale up font sizes proportionally Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove ExternalLinkWrapper; clicking the title now navigates to the contest problem page. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove footnote paragraph and "暫定グレード:" text - Show flask icon left of grade icon when stats (provisional grade) exist - Tooltip on flask explains the provisional grade rule Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Navbar: グレード投票 → 投票 - Default: show empty state (search required) to reduce initial load - Search: limit to 20 results, sorted by task_id desc (newer first) - Table: outer border + rounded corners, lighter row dividers - Column order: グレード | 問題名 | 出典 | 票数 - Grade column: flask icon for provisional grades, "-" when no grade - 問題名: add external link icon to problem page - 出典: use getContestNameLabel helper - service: add grade field to TaskWithVoteInfo, sort desc Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
task_id includes the problem letter suffix (_a, _b, ...) and localeCompare is locale-dependent. contest_id comparison is consistent with the existing compareByContestIdAndTaskId helper. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add task_table_index to TaskWithVoteInfo so the existing helper can be used. This matches the grade-based view sort order: contest type priority → contest_id desc → task_table_index asc. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Grade button itself is unchanged; flask appears to its right inside an inline-flex wrapper. Visible only when task grade is PENDING and an estimated grade from votes is being displayed. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Widen compareByContestIdAndTaskId signature to accept any object with
contest_id/task_table_index, removing double cast in +page.svelte
- Replace find() loop with Map in buildDonutSegments (O(n+m))
- Extract segLabel {@const} in VoteDonutChart to remove duplicate ternary
- Add aria-label to FlaskConical in VotableGrade
- Add aria-label to external link icon in votes list page
- Add qGrades/dGrades tests to grade_options.test.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthrough投票機能に新しいドーナツチャート表示を追加し、タスク情報にグレードとテーブルインデックスを拡張。グレード分類の新しいユーティリティ、検索・ソート機能強化、ナビゲーション・ラベルの更新を実施。 Changes
🎯 3 (Moderate) | ⏱️ ~25 minutesPoem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/features/votes/components/VotableGrade.svelte (1)
54-73: 🧹 Nitpick | 🔵 Trivialビジネスロジックの抽出を検討
onTriggerClick内の fetch 処理はutils/への切り出しが推奨されます(コーディングガイドライン準拠)。ただし、コンポーネント状態と密結合のため、現状でも許容範囲です。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/votes/components/VotableGrade.svelte` around lines 54 - 73, The fetch logic inside the component's onTriggerClick function should be extracted into a reusable utility to satisfy the coding guideline: create a new helper (e.g., getMyVote or fetchMyVote) in the utils folder that accepts taskId and returns the parsed JSON (or throws on error); then replace the inline fetch in onTriggerClick with a call to that util and assign votedGrade from its result while preserving the existing isOpening guard and try/catch/finally flow in onTriggerClick so component state (isOpening, votedGrade) remains correct.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/features/votes/components/VoteDonutChart.svelte`:
- Around line 44-51: 空リングの半径計算が誤っており通常セグメントと形状が一致しないので、VoteDonutChart.svelte の該当
<circle>(現在 r={OUTER_RADIUS} を使っている箇所)を修正して中心半径を (INNER_RADIUS + OUTER_RADIUS) /
2 に変更し、stroke-width は従来どおり OUTER_RADIUS - INNER_RADIUS
を維持してください。これにより空リングの内外径が通常セグメント(INNER/OUTER)と一致します。
In `@src/lib/constants/navbar-links.ts`:
- Line 22: Three e2e tests in votes.spec.ts expect the old heading "グレード投票" and
should be updated to the new label "投票"; update those three assertions to expect
"投票". In the page component +page.svelte for the votes/[slug] route, change the
breadcrumb text currently showing "グレード投票" to "投票". Also find and update related
copy strings such as "この問題のグレードを投票してください" to use the consistent term "投票" (e.g.,
"この問題に投票してください") so navigation, heading, breadcrumb and in-page instruction all
match.
In `@src/routes/votes/`[slug]/+page.svelte:
- Around line 27-29: Move the display-grade decision logic out of the page
script into a utility function: create a helper in utils (e.g.,
getDisplayGrade(taskGrade: TaskGrade, stats?: Stats) that implements the PENDING
fallback (return stats.grade when taskGrade === TaskGrade.PENDING and
stats?.grade exists, otherwise return taskGrade). Replace the inline expression
that defines displayGrade with a call to that util (pass data.task.grade and
data.stats) and import the util at the top of the component; keep references to
TaskGrade, data.task.grade and data.stats.grade intact so callers behave the
same.
- Around line 53-54: Update the anchor(s) in +page.svelte that use
target="_blank" to include "noopener" in their rel attribute; specifically find
the <a> elements with attributes rel="noreferrer external" and target="_blank"
and change the rel value to include noopener (e.g., "noopener noreferrer
external") so the links open securely without allowing the new page to access
window.opener.
- Around line 42-47: The span with id "flask-icon" is not focusable so keyboard
users cannot open the Tooltip; make the trigger element (span id="flask-icon")
keyboard-accessible by adding tabindex="0" and an appropriate aria-label (e.g.,
aria-label="中央値の説明") and, if the span is acting like a control, role="button";
also ensure the Tooltip component (Tooltip triggeredBy="#flask-icon") still
binds to keyboard activation (handle Enter/Space keydown to toggle/show the
Tooltip) so both mouse and keyboard users can open the explanatory tooltip.
In `@src/routes/votes/`+page.svelte:
- Around line 68-71: Extract the duplicated provisional-grade logic into a
shared utility (e.g., a function like computeGradeInfo or two functions
computeIsProvisional and computeDisplayGrade) and use it from both +page.svelte
and VotableGrade.svelte; specifically, centralize the checks that reference
TaskGrade, task.grade and task.estimatedGrade (isProvisional: grade ===
TaskGrade.PENDING && estimatedGrade !== null; displayGrade: grade !==
TaskGrade.PENDING ? grade : (estimatedGrade ?? grade)) so both components call
the shared utility instead of duplicating the logic.
---
Outside diff comments:
In `@src/features/votes/components/VotableGrade.svelte`:
- Around line 54-73: The fetch logic inside the component's onTriggerClick
function should be extracted into a reusable utility to satisfy the coding
guideline: create a new helper (e.g., getMyVote or fetchMyVote) in the utils
folder that accepts taskId and returns the parsed JSON (or throws on error);
then replace the inline fetch in onTriggerClick with a call to that util and
assign votedGrade from its result while preserving the existing isOpening guard
and try/catch/finally flow in onTriggerClick so component state (isOpening,
votedGrade) remains correct.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 648b94d1-760a-4ae0-b64e-59e1e9cedc7c
📒 Files selected for processing (12)
src/features/votes/components/VotableGrade.sveltesrc/features/votes/components/VoteDonutChart.sveltesrc/features/votes/services/vote_statistics.test.tssrc/features/votes/services/vote_statistics.tssrc/features/votes/utils/donut_chart.test.tssrc/features/votes/utils/donut_chart.tssrc/features/votes/utils/grade_options.test.tssrc/features/votes/utils/grade_options.tssrc/lib/constants/navbar-links.tssrc/lib/utils/task.tssrc/routes/votes/+page.sveltesrc/routes/votes/[slug]/+page.svelte
| <circle | ||
| cx={CX} | ||
| cy={CY} | ||
| r={OUTER_RADIUS} | ||
| fill="none" | ||
| stroke="currentColor" | ||
| stroke-width={OUTER_RADIUS - INNER_RADIUS} | ||
| class="text-gray-200 dark:text-gray-700" |
There was a problem hiding this comment.
0票時リングの半径計算がずれており、通常時と形状が一致しません。
Line 47 の r={OUTER_RADIUS} だと、空リングの実効内外径が通常セグメント(INNER/OUTER)と一致しません。
空リングは「中心半径 = (INNER+OUTER)/2」を使ってください。
修正案
- <circle
+ <circle
cx={CX}
cy={CY}
- r={OUTER_RADIUS}
+ r={(OUTER_RADIUS + INNER_RADIUS) / 2}
fill="none"
stroke="currentColor"
stroke-width={OUTER_RADIUS - INNER_RADIUS}
class="text-gray-200 dark:text-gray-700"
opacity="0.5"
/>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <circle | |
| cx={CX} | |
| cy={CY} | |
| r={OUTER_RADIUS} | |
| fill="none" | |
| stroke="currentColor" | |
| stroke-width={OUTER_RADIUS - INNER_RADIUS} | |
| class="text-gray-200 dark:text-gray-700" | |
| <circle | |
| cx={CX} | |
| cy={CY} | |
| r={(OUTER_RADIUS + INNER_RADIUS) / 2} | |
| fill="none" | |
| stroke="currentColor" | |
| stroke-width={OUTER_RADIUS - INNER_RADIUS} | |
| class="text-gray-200 dark:text-gray-700" | |
| opacity="0.5" | |
| /> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/features/votes/components/VoteDonutChart.svelte` around lines 44 - 51,
空リングの半径計算が誤っており通常セグメントと形状が一致しないので、VoteDonutChart.svelte の該当 <circle>(現在
r={OUTER_RADIUS} を使っている箇所)を修正して中心半径を (INNER_RADIUS + OUTER_RADIUS) / 2
に変更し、stroke-width は従来どおり OUTER_RADIUS - INNER_RADIUS
を維持してください。これにより空リングの内外径が通常セグメント(INNER/OUTER)と一致します。
| const largeArcFlag = endAngle - startAngle > Math.PI ? 1 : 0; | ||
|
|
||
| return [ | ||
| `M ${x1} ${y1}`, | ||
| `A ${outerRadius} ${outerRadius} 0 ${largeArcFlag} 1 ${x2} ${y2}`, | ||
| `L ${x3} ${y3}`, | ||
| `A ${innerRadius} ${innerRadius} 0 ${largeArcFlag} 0 ${x4} ${y4}`, | ||
| 'Z', | ||
| ].join(' '); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In SVG path syntax, when using the A command, if the arc start point and end point are identical, is a full circle guaranteed to render? What is the recommended way to draw a full donut segment (100%)?
💡 Result:
No, a full circle is not guaranteed to render when using the A command with identical start and end points. The SVG specification states that if the endpoint of an elliptical arc segment is identical to the current point, the arc segment is entirely omitted. This is a defined error-handling rule for out-of-range parameters in elliptical arcs. The recommended way to draw a full donut segment (100% ring) in SVG path syntax is to use two consecutive elliptical arc commands that together complete exactly 360 degrees, typically starting from a point on the circumference: M cx cy-r a r r 0 1 0 -(r2) 0 a r r 0 1 0 (r2) 0 Z Where (cx, cy) is the center and r is the radius (adjust for inner/outer radii for thickness). This creates a closed path approximating a perfect circle. Alternatively, use elements with stroke for simpler donuts, but since the question specifies path syntax, the dual-arc path is standard. For donuts, draw outer ring path minus inner hole path, or use compound paths/subpaths.
Citations:
- 1: https://www.w3.org/TR/SVG11/paths.html
- 2: https://svgwg.org/svg2-draft/paths.html
- 3: https://www.w3.org/TR/SVG11/implnote.html
- 4: https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorials/SVG_from_scratch/Paths
- 5: https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
- 6: https://stackoverflow.com/questions/5737975/circle-drawing-with-svgs-arc-path
- 7: https://redev.rocks/articles/08-arcs
🏁 Script executed:
fd -t f donut_chart.tsRepository: AtCoder-NoviSteps/AtCoderNoviSteps
Length of output: 117
🏁 Script executed:
find . -path "*test*" -name "*donut*" -type fRepository: AtCoder-NoviSteps/AtCoderNoviSteps
Length of output: 124
🏁 Script executed:
find . -path "*spec*" -name "*donut*" -type fRepository: AtCoder-NoviSteps/AtCoderNoviSteps
Length of output: 60
🏁 Script executed:
cat -n src/features/votes/utils/donut_chart.tsRepository: AtCoder-NoviSteps/AtCoderNoviSteps
Length of output: 3959
🏁 Script executed:
cat -n src/features/votes/utils/donut_chart.test.tsRepository: AtCoder-NoviSteps/AtCoderNoviSteps
Length of output: 3389
100%セグメント時にSVGパスが描画されない重大な問題があります。
arcPath() は全円(endAngle - startAngle ≈ 2π)の場合に対応していません。SVG仕様により、開始点と終了点が同一の場合、arcコマンドは完全に省略されるため、100%セグメントのドーナツ外周が描画されません。
現在のテストは buildDonutSegments で100%セグメント生成を確認していますが、arcPath に対する完全円角度のテストが欠落しており、コーディングガイドラインの「各関数は adjacent unit test を有すること」を満たしていません。
修正案のように、全周近傍で分岐して2つのarcコマンドで360度を描画する必要があります。
修正案(full-circle分岐)
export function arcPath(
cx: number,
cy: number,
outerRadius: number,
innerRadius: number,
startAngle: number,
endAngle: number,
): string {
+ const sweep = endAngle - startAngle;
+ if (Math.abs(sweep) >= TAU - 1e-9) {
+ const sxOuter = cx + outerRadius * Math.cos(startAngle);
+ const syOuter = cy + outerRadius * Math.sin(startAngle);
+ const mxOuter = cx + outerRadius * Math.cos(startAngle + Math.PI);
+ const myOuter = cy + outerRadius * Math.sin(startAngle + Math.PI);
+ const sxInner = cx + innerRadius * Math.cos(startAngle);
+ const syInner = cy + innerRadius * Math.sin(startAngle);
+ const mxInner = cx + innerRadius * Math.cos(startAngle + Math.PI);
+ const myInner = cy + innerRadius * Math.sin(startAngle + Math.PI);
+
+ return [
+ `M ${sxOuter} ${syOuter}`,
+ `A ${outerRadius} ${outerRadius} 0 1 1 ${mxOuter} ${myOuter}`,
+ `A ${outerRadius} ${outerRadius} 0 1 1 ${sxOuter} ${syOuter}`,
+ `L ${sxInner} ${syInner}`,
+ `A ${innerRadius} ${innerRadius} 0 1 0 ${mxInner} ${myInner}`,
+ `A ${innerRadius} ${innerRadius} 0 1 0 ${sxInner} ${syInner}`,
+ 'Z',
+ ].join(' ');
+ }
+
const x1 = cx + outerRadius * Math.cos(startAngle);
const y1 = cy + outerRadius * Math.sin(startAngle);
const x2 = cx + outerRadius * Math.cos(endAngle);
const y2 = cy + outerRadius * Math.sin(endAngle);
const x3 = cx + innerRadius * Math.cos(endAngle);
const y3 = cy + innerRadius * Math.sin(endAngle);
const x4 = cx + innerRadius * Math.cos(startAngle);
const y4 = cy + innerRadius * Math.sin(startAngle);
const largeArcFlag = endAngle - startAngle > Math.PI ? 1 : 0;加えて、arcPath に対して100%セグメントケースをテストするテストケースを追加してください。
| { title: `問題集`, path: WORKBOOKS_PAGE }, | ||
| { title: `一覧表`, path: PROBLEMS_PAGE }, | ||
| { title: `グレード投票`, path: VOTES_PAGE }, | ||
| { title: `投票`, path: VOTES_PAGE }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: アプリケーション内で "グレード投票" または "投票" の用語が一貫して使用されているかを確認
# "グレード投票" の使用箇所を検索
echo "=== 'グレード投票' の使用箇所 ==="
rg -n 'グレード投票' --type ts --type tsx --type js --type jsx
# "投票" の使用箇所を検索(コンテキスト付き)
echo -e "\n=== '投票' の使用箇所(ナビゲーション/タイトル関連) ==="
rg -n -C2 '投票' --type ts --type tsx --type js --type jsx -g '!node_modules' | head -50Repository: AtCoder-NoviSteps/AtCoderNoviSteps
Length of output: 209
🏁 Script executed:
# ファイルの内容を確認
cat -n src/lib/constants/navbar-links.ts
# 正しいファイル拡張子でグレード投票と投票を検索
echo "=== グレード投票 の検索 ==="
rg 'グレード投票' --type-list | grep -i ts && rg 'グレード投票' --type ts .
echo "=== 投票 の検索(ナビゲーション、ページ関連) ==="
rg '投票' -g '*.ts' -g '*.js' -g '*.tsx' -g '*.jsx' | head -20Repository: AtCoder-NoviSteps/AtCoderNoviSteps
Length of output: 2775
🏁 Script executed:
# e2e テストの該当部分を確認
cat -n e2e/votes.spec.ts | grep -A5 -B5 'グレード投票'
# 投票ページの実装を確認
find . -path ./node_modules -prune -o -name "+page.svelte" -o -name "+page.ts" | grep -i vote
# ページのメタデータやヘッダー設定を確認
rg -n "グレード投票|投票" -g "*.svelte" -g "*.ts" -g "*.js" src/routes/votesRepository: AtCoder-NoviSteps/AtCoderNoviSteps
Length of output: 2404
🏁 Script executed:
# 投票ページのメインページの完全な内容を確認
cat -n src/routes/votes/+page.svelte
# 詳細ページの該当部分も確認
cat -n src/routes/votes/[slug]/+page.svelte | head -100Repository: AtCoder-NoviSteps/AtCoderNoviSteps
Length of output: 9754
e2e テストの失敗が発生します。ナビゲーションバーのラベルを「投票」に変更していますが、以下の項目が更新されていません:
- e2e/votes.spec.ts の3つのテスト(17行目、36行目、107行目)が、ページ見出しに「グレード投票」を期待しており、失敗します
- src/routes/votes/[slug]/+page.svelte 35行目のパンくずリンクが「グレード投票」のまま。ナビゲーションと一貫性がありません
- 関連テキスト(「この問題のグレードを投票してください」)との用語の整合性を確認してください
対応が必要:
- e2e テスト3箇所を「投票」に更新
- パンくずリンクテキストも「投票」に統一
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/constants/navbar-links.ts` at line 22, Three e2e tests in
votes.spec.ts expect the old heading "グレード投票" and should be updated to the new
label "投票"; update those three assertions to expect "投票". In the page component
+page.svelte for the votes/[slug] route, change the breadcrumb text currently
showing "グレード投票" to "投票". Also find and update related copy strings such as
"この問題のグレードを投票してください" to use the consistent term "投票" (e.g., "この問題に投票してください") so
navigation, heading, breadcrumb and in-page instruction all match.
| const displayGrade = $derived( | ||
| data.task.grade === TaskGrade.PENDING && data.stats?.grade ? data.stats.grade : data.task.grade, | ||
| ); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
表示グレード決定ロジックは utils に切り出してください。
Line 27-29 は業務ルール(PENDING 時の代替表示)なので、route の <script> 直書きではなく utils に抽出したほうが保守性と再利用性が上がります。
As per coding guidelines, "Business logic belongs in utils/, not inside <script> blocks."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/routes/votes/`[slug]/+page.svelte around lines 27 - 29, Move the
display-grade decision logic out of the page script into a utility function:
create a helper in utils (e.g., getDisplayGrade(taskGrade: TaskGrade, stats?:
Stats) that implements the PENDING fallback (return stats.grade when taskGrade
=== TaskGrade.PENDING and stats?.grade exists, otherwise return taskGrade).
Replace the inline expression that defines displayGrade with a call to that util
(pass data.task.grade and data.stats) and import the util at the top of the
component; keep references to TaskGrade, data.task.grade and data.stats.grade
intact so callers behave the same.
| <span id="flask-icon" class="cursor-help text-gray-500 dark:text-gray-400"> | ||
| <FlaskConical class="w-5 h-5" /> | ||
| </span> | ||
| <Tooltip triggeredBy="#flask-icon" placement="bottom"> | ||
| 3票以上集まると中央値が暫定グレードとして一覧表に反映されます。 | ||
| </Tooltip> |
There was a problem hiding this comment.
Tooltip トリガーがキーボード操作不可です。
Line 42 の span はフォーカスできないため、キーボード利用者が補足説明を開けません。tabindex="0" と aria-label(必要なら role="button")を追加してください。
修正案
- <span id="flask-icon" class="cursor-help text-gray-500 dark:text-gray-400">
+ <span
+ id="flask-icon"
+ class="cursor-help text-gray-500 dark:text-gray-400"
+ tabindex="0"
+ role="button"
+ aria-label="暫定グレードの説明を表示"
+ >
<FlaskConical class="w-5 h-5" />
</span>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <span id="flask-icon" class="cursor-help text-gray-500 dark:text-gray-400"> | |
| <FlaskConical class="w-5 h-5" /> | |
| </span> | |
| <Tooltip triggeredBy="#flask-icon" placement="bottom"> | |
| 3票以上集まると中央値が暫定グレードとして一覧表に反映されます。 | |
| </Tooltip> | |
| <span | |
| id="flask-icon" | |
| class="cursor-help text-gray-500 dark:text-gray-400" | |
| tabindex="0" | |
| role="button" | |
| aria-label="暫定グレードの説明を表示" | |
| > | |
| <FlaskConical class="w-5 h-5" /> | |
| </span> | |
| <Tooltip triggeredBy="#flask-icon" placement="bottom"> | |
| 3票以上集まると中央値が暫定グレードとして一覧表に反映されます。 | |
| </Tooltip> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/routes/votes/`[slug]/+page.svelte around lines 42 - 47, The span with id
"flask-icon" is not focusable so keyboard users cannot open the Tooltip; make
the trigger element (span id="flask-icon") keyboard-accessible by adding
tabindex="0" and an appropriate aria-label (e.g., aria-label="中央値の説明") and, if
the span is acting like a control, role="button"; also ensure the Tooltip
component (Tooltip triggeredBy="#flask-icon") still binds to keyboard activation
(handle Enter/Space keydown to toggle/show the Tooltip) so both mouse and
keyboard users can open the explanatory tooltip.
| rel="noreferrer external" | ||
| target="_blank" |
There was a problem hiding this comment.
target="_blank" のリンクに noopener を追加してください。
Line 53-54 は noreferrer があっても、noopener を明示しておくほうが安全です。
- 例:
rel="noopener noreferrer external"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/routes/votes/`[slug]/+page.svelte around lines 53 - 54, Update the
anchor(s) in +page.svelte that use target="_blank" to include "noopener" in
their rel attribute; specifically find the <a> elements with attributes
rel="noreferrer external" and target="_blank" and change the rel value to
include noopener (e.g., "noopener noreferrer external") so the links open
securely without allowing the new page to access window.opener.
| {@const isProvisional = | ||
| task.grade === TaskGrade.PENDING && task.estimatedGrade !== null} | ||
| {@const displayGrade = | ||
| task.grade !== TaskGrade.PENDING ? task.grade : (task.estimatedGrade ?? task.grade)} |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
暫定グレード判定ロジックの重複
この判定ロジックは VotableGrade.svelte (lines 47-49) と重複しています。共通のユーティリティ関数への抽出を検討してください。
isProvisional:grade === PENDING && estimatedGrade !== nulldisplayGrade:grade !== PENDING ? grade : (estimatedGrade ?? grade)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/routes/votes/`+page.svelte around lines 68 - 71, Extract the duplicated
provisional-grade logic into a shared utility (e.g., a function like
computeGradeInfo or two functions computeIsProvisional and computeDisplayGrade)
and use it from both +page.svelte and VotableGrade.svelte; specifically,
centralize the checks that reference TaskGrade, task.grade and
task.estimatedGrade (isProvisional: grade === TaskGrade.PENDING &&
estimatedGrade !== null; displayGrade: grade !== TaskGrade.PENDING ? grade :
(estimatedGrade ?? grade)) so both components call the shared utility instead of
duplicating the logic.
…ps into feature/add_circle_graph
投票機能に関する改修です。
以下の変更を行いました。
ローカルでcoderabbitによるレビューを4回行いましたが、念のためご確認よろしくお願いいたします。
Summary by CodeRabbit
リリースノート
New Features
UI Changes