前幾天已經分別完成:
這些功能在開發時都個別測試過,如果今天再把相同操作全部重做一次,雖然也算測試,卻不一定能找出真正影響使用體驗的問題。
因此,Day 29 我改變了測試方式。
今天不再加入新功能,也不只是依照固定清單重複操作,而是從實際使用 App 的過程中觀察:
資料雖然正確,但呈現在畫面上的結果是否真的合理?
這次就在「大挑戰」模式中,發現了一個過去沒有注意到的問題。
我使用 Gemini 產生「太陽系」5 張學習卡,接著切換到大挑戰模式。
其中一題是:
地球是太陽系裡的第幾顆行星?
但畫面上的選項卻是:
我們的太陽系
美麗的地球
大火球太陽
【圖 1:修改前,第幾顆行星的題目卻顯示卡片標題】

另一題是:
月亮本身會發光嗎?
選項則是:
我們的太陽系
地球的好朋友月亮
美麗的地球
【圖 2:修改前,會不會的題目仍然顯示卡片標題】

雖然選項和太陽系主題有關,但它們並不能直接回答題目。
如果題目問「第幾顆行星」,使用者預期看到的應該是「第三顆」;如果題目問「會不會發光」,選項中就應該出現「會」或「不會」。
因此,今天將這個問題記錄為:
CH-01:大挑戰使用
question顯示題目,選項卻使用title,造成題目與選項語意不一致。
看到題目和選項對不上時,第一個需要確認的是 Gemini 是否產生了錯誤資料。
我先查看產生卡片時的 Console。
Gemini 回傳的其中兩張卡片如下:
title:美麗的地球
question:地球是太陽系裡的第幾顆行星?
answer:第三顆
title:地球的好朋友月亮
question:月亮本身會發光嗎?
answer:不會
這表示 Gemini 已經正確產生:
接著再查看轉換完成的 LearningCard[],裡面的 question 與 answer 仍然正確。
因此,問題並不是發生在 Gemini Structured Output,也不是發生在資料轉換階段。
為了確認重新整理後的資料是否被改壞,我也查看了 LocalStorage 中的太陽系主題。
其中第四張卡片保存的是:
{
"title": "美麗的地球",
"question": "地球是太陽系裡的第幾顆行星?",
"answer": "第三顆"
}
第五張卡片保存的是:
{
"title": "地球的好朋友月亮",
"question": "月亮本身會發光嗎?",
"answer": "不會"
}
LocalStorage 中的內容也沒有問題。
到這裡可以排除:
LearningCard[] 轉換完整追蹤結果變成:
Gemini Structured Response:正確
↓
LearningCard[]:正確
↓
AppTheme[]:正確
↓
LocalStorage:正確
↓
QuizMode 顯示選項時出現語意落差
問題範圍因此縮小到大挑戰模式的畫面邏輯。
原本的 QuizMode.tsx 會取得目前題目的卡片,再隨機加入另外兩張卡片作為干擾選項。
概念如下:
目前題目的卡片
+
另外兩張隨機卡片
↓
隨機排列三張 LearningCard
這個選項產生方式本身沒有錯,因為它可以確保目前卡片一定存在於選項中。
正確答案的判斷也不是比較文字,而是比較 Card ID:
if (card.id === currentCard.id) {
// Correct
}
真正的問題出現在選項顯示內容。
原本畫面使用:
<div className="text-xl font-black">
{option.title}
</div>
因此,即使題目使用的是:
currentCard.question
選項顯示的卻是:
option.title
這使大挑戰實際上變成:
問題:地球是第幾顆行星?
選項:美麗的地球
程式仍然可以透過 Card ID 判定「美麗的地球」是目前題目的卡片,但從使用者角度來看,選項並沒有回答問題。
這不是 Gemini 資料錯誤,而是資料欄位在畫面上的使用方式不一致。
確認問題後,我沒有要求 Gemini 重寫整個大挑戰,也沒有修改 Gemini Prompt 或資料結構。
這次只要求最小必要修改:
請檢查目前 React + TypeScript Learning Card App 的大挑戰模式,
只修正題目使用 question、選項卻使用 title 的問題。
【修改目標】
1. 題目繼續顯示 currentCard.question。
2. 選項文字改成顯示 option.answer。
3. 保留目前「正確卡片加兩張隨機干擾卡片」的方式。
4. 保留以 card.id === currentCard.id 判定正確答案。
5. 將「小朋友找找看」改成「選出正確答案」。
6. 答對回饋改用 card.answer,不再使用 card.title。
7. 答錯回饋不要再顯示卡片 title。
8. 保留 Emoji、音效、語音、動畫與下一題流程。
【限制】
- 優先只修改 src/components/QuizMode.tsx
- 不修改 Gemini Prompt
- 不修改 Structured Output Schema
- 不修改 LearningCard 與 AppTheme 型別
- 不修改 LocalStorage
- 不修改星星計算方式
- 不修改探索卡
- 不修改 Generate、Regenerate、Human Edit 與列印功能
- 採用最小必要修改
這次的重點不是增加更多功能,而是修正資料已經正確、畫面卻使用錯誤欄位的問題。
Gemini 執行約 113 秒,修改了一個檔案:
src/components/QuizMode.tsx
沒有修改其他元件、資料型別或 LocalStorage。
原本:
<div className="text-xl font-black">
{option.title}
</div>
修改為:
<div className="text-xl font-black leading-snug">
{option.answer}
</div>
現在大挑戰的資料對應方式變成:
題目:currentCard.question
選項:option.answer
例如:
地球是太陽系裡的第幾顆行星?
第三顆
會
不會
雖然錯誤選項仍然來自其他卡片的答案,但至少每一個選項現在都是「回答內容」,而不是卡片名稱。
原本選項顯示卡片標題時,也會顯示對應的 pinyin。
改為答案選項後,這些拼音不一定和答案文字相符,因此 Gemini 一併移除了選項中的 pinyin,避免再次出現資料語意不一致。
Emoji 則繼續保留,維持原本適合幼兒辨識的視覺設計。
原本答對後可能顯示:
太棒了!就是美麗的地球!
或:
好厲害!找到了美麗的地球!
修改後改用實際答案:
`太棒了!答案就是「${card.answer}」!🎉`
以及:
`好厲害!選對了「${card.answer}」!🎈`
這樣題目、選項與回饋會使用同一個答案內容。
原本答錯時會顯示使用者點選的卡片名稱:
這是大火球太陽喔!再摸摸看哪一個是呢?
但選項已經改成答案後,繼續提到卡片標題會令人困惑。
因此修改成較通用的提示:
不對喔!再想想看哪一個才是正確答案呢?
題目上方原本顯示:
小朋友找找看
修改為:
選出正確答案
讓使用者一進入大挑戰,就能理解這是一個選擇正確答案的問答模式。
雖然畫面改為顯示 answer,但 Gemini 沒有將正確答案判斷改成字串比較。
正確判斷仍然使用:
card.id === currentCard.id
這樣做有幾個好處:
畫面負責顯示 answer,程式則使用 Card ID 確認選到的是不是目前題目的卡片。
使用者看到:answer
程式判斷:card.id
兩者各自負責不同工作。
完成修改後,我再次使用相同的「太陽系」主題測試大挑戰。
題目:
太陽會發光發熱嗎?
修改後的選項包括:
會
第三顆
不會
選擇「會」後,畫面正確顯示答對回饋。
【圖 3:修改後,太陽題正確顯示「會」】

題目:
地球是太陽系裡的第幾顆行星?
修改後的選項包括:
第三顆
會
不會
選擇「第三顆」後,按鈕變成綠色,並顯示:
好厲害!選對了「第三顆」!
【圖 4:修改後,地球題正確顯示「第三顆」】

| 測試項目 | 結果 |
|---|---|
題目繼續使用 question |
通過 |
選項改為顯示 answer |
通過 |
| 正確答案存在於選項中 | 通過 |
| 選項仍會隨機排列 | 通過 |
| Card ID 判斷正常 | 通過 |
| 答對回饋顯示答案 | 通過 |
| 答錯後可繼續選擇 | 通過 |
| 下一題功能正常 | 通過 |
| Emoji 正常顯示 | 通過 |
| 語音與動畫正常 | 通過 |
| TypeScript 檢查 | 0 錯誤 |
| Build | 成功 |
如果只確認按鈕能不能點、大挑戰能不能切換到下一題,原本的功能其實也算「可以運作」。
但從使用者角度來看:
題目問第幾顆行星
選項卻顯示卡片名稱
這並不是一個合理的問答體驗。
這次測試讓我理解:
程式沒有報錯,不代表功能設計就是正確的。
整合測試除了確認程式不會當機,也要檢查資料從來源到畫面的語意是否一致。
這次的資料追蹤包含:
Gemini Response
↓
LearningCard[]
↓
AppTheme[]
↓
LocalStorage
↓
QuizMode
↓
畫面選項
逐層檢查後,才能確定問題不是 AI 內容品質,也不是資料在保存時損壞,而是 QuizMode 最後選擇了不適合的顯示欄位。
這次 Gemini 只修改一個檔案:
src/components/QuizMode.tsx
沒有因為大挑戰顯示錯誤就重新設計:
實際需要修正的只是:
option.title
↓
option.answer
以及與選項語意相關的提示與回饋文字。
修改範圍越清楚,就越容易:
前面已經驗證過的功能,不需要每天完全重跑。可以從實際操作中找出跨元件或資料流程上的問題,再進行針對性測試。
Gemini Response 和 LearningCard[] 都正確,代表不應該急著修改 Prompt。
檢查實際保存的 JSON,可以判斷資料是否在重新整理前後被改壞。
title 是卡片名稱,answer 才是問題答案。兩者都是字串,但不能因為型別相同就互相替代。
TypeScript 和 Build 只能確認程式可以編譯,無法判斷題目與選項是否合理。
這次只修改 QuizMode.tsx,就完成了問題修正,也降低了其他功能產生回歸問題的風險。
今天完成了:
LearningCard[]
QuizMode 使用錯誤顯示欄位title 改為 answer
經過這次修正,大挑戰不只是程式可以運作,題目、選項及回饋也真正形成一致的問答流程。
明天將進入系列最後一天:
Day 30|30 天打造 AI 學習卡:成果總結、限制與下一步
最後會整理這 30 天完成的功能、AI 協作方式、目前仍存在的限制,以及這個 Learning Card App 未來可以繼續發展的方向。