AI 教學

【Vibe Coding 教學】如何用 Codex+Scriptable 製作香港交通到站時間小組件?

【Vibe Coding 教學】如何用 Codex+Scriptable 製作香港交通到站時間小組件?

每天出門前,你可能都會重複做同一件事:

打開交通 App、搜尋路線、選擇上車站,再查看下一班車還有多少分鐘到達。

如果每天乘搭的路線差不多,有沒有可能將最常用的交通資料,直接放在 iPhone 主畫面?

今次我們會嘗試利用 Codex+Scriptable,製作一個個人使用的香港交通到站時間 Widget。

它可以顯示:

  • 自己收藏的交通路線

  • 上車站

  • 行車方向或目的地

  • 最近幾班車還有多少分鐘到達

  • 資料更新時間

這並不是一個需要提交到 App Store 的完整 iPhone App,也不是網頁 App,而是一個在 Scriptable 中執行、可以放到 iPhone 主畫面的個人工具。

這篇教學的目標,也不是要求新手立即學懂完整 App 開發。

我們真正希望學員掌握的是:

把一個真實生活問題拆成清楚的產品需求,
再利用 AI 協助規劃、寫程式、測試及逐步改良,
最後做成一個自己真正用得到的小工具。
把一個真實生活問題拆成清楚的產品需求,
再利用 AI 協助規劃、寫程式、測試及逐步改良,
最後做成一個自己真正用得到的小工具。
把一個真實生活問題拆成清楚的產品需求,
再利用 AI 協助規劃、寫程式、測試及逐步改良,
最後做成一個自己真正用得到的小工具。

Codex 可以協助開發者閱讀、編輯及執行程式,處理功能開發、修正問題、重構及測試等工作;Scriptable 則是一個在 iOS 上利用 JavaScript 建立自動化及主畫面 Widget 的工具。


今次會做出甚麼?

我們會製作一個 Scriptable 中型 Widget。

第一版的項目範圍預計包括:

  • 九巴:路線收藏、車站收藏

  • 龍運:路線收藏、車站收藏

  • 新大嶼山巴士:路線收藏

  • 綠色專線小巴:路線收藏

  • 港鐵:收藏指定路線、車站及方向

  • 輕鐵:收藏指定車站

今次不把紅色小巴列入第一版。

原因不是紅色小巴永遠不能加入,而是新手項目應優先使用文件較完整、結構較清楚及方便測試的公開數據來源。如果未能確認穩定而合適的實時數據,便不應要求 AI 自行猜測資料。

香港政府開放數據平台目前提供多項公共交通實時到站數據,包括九巴/龍運、綠色專線小巴及其他營辦商資料。綠色專線小巴的官方數據資源,例如會提供每個車站連續三班小巴的實時到站資訊。

不過,不同交通工具的數據格式並不相同。

因此,整個項目不會一次過完成所有交通工具,而是按照以下次序逐步製作:

固定一條巴士路線
確認可以讀取到站資料
完成 Widget 畫面
加入收藏功能
加入多個 Widget
逐一接駁其他交通
固定一條巴士路線
確認可以讀取到站資料
完成 Widget 畫面
加入收藏功能
加入多個 Widget
逐一接駁其他交通
固定一條巴士路線
確認可以讀取到站資料
完成 Widget 畫面
加入收藏功能
加入多個 Widget
逐一接駁其他交通


為甚麼選擇 Codex+Scriptable?

對新手而言,最重要的不是一開始學齊所有開發知識,而是先完成一件能夠實際使用的作品。

如果由零開始製作原生 iOS App,通常需要接觸:

  • Xcode

  • Swift

  • WidgetKit

  • App 資料架構

  • Apple Developer 帳戶

  • 程式簽署

  • 裝置測試

  • App Store 發佈流程

這些知識都有價值,但不一定適合作為第一個 Vibe Coding 練習。

今次採用的分工是:

Codex 負責

  • 協助拆解需求

  • 撰寫產品規格

  • 生成 JavaScript

  • 解釋程式結構

  • 修改錯誤

  • 重構程式

  • 設計測試情況

  • 檢查例外狀態

Scriptable 負責

  • 在 iPhone 上執行 JavaScript

  • 向交通 API 發出請求

  • 儲存收藏設定

  • 建立 Widget 畫面

  • 接收 Widget Parameter

  • 在主畫面顯示結果

Scriptable 官方文件提供 Request 處理 HTTP 請求、FileManager 在裝置讀寫檔案,以及 ListWidget 建立主畫面 Widget。

這樣,學員可以先把注意力放在一項更核心的能力上:

如何把一個生活問題,變成 AI 看得明白、程式能夠執行、 使用者真正用得到的產品需求。


Step 1|先把生活問題說清楚

很多新手第一次使用 AI 寫程式時,會直接輸入:

幫我做一個 App。
幫我做一個 App。
幫我做一個 App。

這句話的範圍太大。

AI 不知道你想製作的是:

  • 原生 iPhone App

  • 網頁 App

  • 桌面程式

  • 自動化 Script

  • 主畫面 Widget

  • 捷徑流程

它也不知道是否需要登入、地圖、通知、付款、資料庫或後台系統。

提示詞(可複製):

我想製作一個免費的 iPhone 主畫面 Widget,
用來顯示香港交通到站時間。

我是新手,不想使用 Xcode,
希望利用 Codex 協助開發,
並在 Scriptable 執行。

使用者可以收藏自己的路線或車站,
亦可以加入多個 Widget,分別顯示不同收藏。

請先協助我拆解第一版應該做甚麼。
暫時不要寫程式碼。
我想製作一個免費的 iPhone 主畫面 Widget,
用來顯示香港交通到站時間。

我是新手,不想使用 Xcode,
希望利用 Codex 協助開發,
並在 Scriptable 執行。

使用者可以收藏自己的路線或車站,
亦可以加入多個 Widget,分別顯示不同收藏。

請先協助我拆解第一版應該做甚麼。
暫時不要寫程式碼。
我想製作一個免費的 iPhone 主畫面 Widget,
用來顯示香港交通到站時間。

我是新手,不想使用 Xcode,
希望利用 Codex 協助開發,
並在 Scriptable 執行。

使用者可以收藏自己的路線或車站,
亦可以加入多個 Widget,分別顯示不同收藏。

請先協助我拆解第一版應該做甚麼。
暫時不要寫程式碼。

這段 Prompt 已經清楚交代:

  • 使用場景:iPhone 主畫面

  • 產品形式:Widget

  • 執行工具:Scriptable

  • 使用者程度:新手

  • 核心功能:路線收藏及到站時間

  • 開發方式:先拆解,再寫程式

這一步的重點並不是生成程式碼,而是讓 Codex 先正確理解問題。

Step 2|讓 Codex 先寫規格,不要急着寫程式

新手常見的錯誤,是需求仍然模糊,便直接要求 AI 輸出幾百行程式碼。

即使程式看起來完整,你也可能不知道:

  • 它實際完成了哪些功能

  • 使用了甚麼資料來源

  • 哪些功能仍未完成

  • 如何判斷結果是否正確

  • 出錯時應該檢查哪一部分

提示詞(可複製):

請先不要寫程式。

請協助我把這個交通到站時間 Widget 拆成:

1. 第一版必做功能
2. 之後才加入的功能
3. 使用者操作流程
4. 所需資料來源
5. 需要測試的情況
6. 可能遇到的限制
7. 第一版的完成標準

請以新手能理解的方式說明。
請先不要寫程式。

請協助我把這個交通到站時間 Widget 拆成:

1. 第一版必做功能
2. 之後才加入的功能
3. 使用者操作流程
4. 所需資料來源
5. 需要測試的情況
6. 可能遇到的限制
7. 第一版的完成標準

請以新手能理解的方式說明。
請先不要寫程式。

請協助我把這個交通到站時間 Widget 拆成:

1. 第一版必做功能
2. 之後才加入的功能
3. 使用者操作流程
4. 所需資料來源
5. 需要測試的情況
6. 可能遇到的限制
7. 第一版的完成標準

請以新手能理解的方式說明。

這樣做有幾個好處:

  1. 學員會更清楚自己正在製作甚麼

  2. Codex 後續生成程式時較不容易偏題

  3. 測試時有清晰的驗收標準

  4. 可以提早發現要求是否過多

  5. 容易把複雜項目拆成較小步驟

第一版可以先限制為:

  • 固定一條路線

  • 固定一個上車站

  • 固定一個方向

  • 顯示最近三班車

  • 顯示更新時間

  • 使用中型 Widget

  • 能在 Scriptable 內預覽

  • 網絡失敗時顯示清楚訊息

這就是項目的 最小可行版本,亦即 MVP。

第一版不需要功能最多,只需要證明整條流程可以運作。


Step 3|先做最小可行版本

第一版不要立即支援所有交通工具。

我們先製作一個固定版本,例如:

  • 路線:269C

  • 上車站:天頌苑

  • 方向:往觀塘碼頭

  • 顯示最近三班車

  • 顯示更新時間

提示詞(可複製):

請協助我製作一個 Scriptable 中型 Widget
的最小可行版本。

固定顯示:

- 路線:269C
- 上車站:天頌苑
- 方向:往觀塘碼頭
- 最近三班預計到站時間
- 資料更新時間

要求:

1. 使用可核對的官方到站數據
2. 使用 JavaScript
3. 可以直接貼入 Scriptable 執行
4. UI 適合 iPhone 中型 Widget
5. 網絡失敗時顯示清楚訊息
6. 沒有班次時顯示「暫未有班次資料」
7. 不要向一般使用者顯示 TypeError 等技術錯誤
8. 先解釋程式結構,再提供完整程式碼
請協助我製作一個 Scriptable 中型 Widget
的最小可行版本。

固定顯示:

- 路線:269C
- 上車站:天頌苑
- 方向:往觀塘碼頭
- 最近三班預計到站時間
- 資料更新時間

要求:

1. 使用可核對的官方到站數據
2. 使用 JavaScript
3. 可以直接貼入 Scriptable 執行
4. UI 適合 iPhone 中型 Widget
5. 網絡失敗時顯示清楚訊息
6. 沒有班次時顯示「暫未有班次資料」
7. 不要向一般使用者顯示 TypeError 等技術錯誤
8. 先解釋程式結構,再提供完整程式碼
請協助我製作一個 Scriptable 中型 Widget
的最小可行版本。

固定顯示:

- 路線:269C
- 上車站:天頌苑
- 方向:往觀塘碼頭
- 最近三班預計到站時間
- 資料更新時間

要求:

1. 使用可核對的官方到站數據
2. 使用 JavaScript
3. 可以直接貼入 Scriptable 執行
4. UI 適合 iPhone 中型 Widget
5. 網絡失敗時顯示清楚訊息
6. 沒有班次時顯示「暫未有班次資料」
7. 不要向一般使用者顯示 TypeError 等技術錯誤
8. 先解釋程式結構,再提供完整程式碼

這一步只需要驗證三件事:

  1. 官方到站資料能否正常讀取

  2. Scriptable 能否執行程式

  3. Widget 能否正常顯示內容

不要急着追求漂亮介面、所有交通工具或完整收藏系統。

先讓最基本版本正常運作。

需要留意,畫面上顯示的路線名稱及車站名稱,不一定就是 API 查詢時使用的識別碼。實際程式仍需按照官方數據中的路線、方向、服務類型及車站 ID 配對,不能單靠文字名稱猜測。


Step 4|在 iPhone 安裝 Scriptable

接下來轉到 iPhone 操作。

  1. 打開 App Store

  2. 搜尋 Scriptable

  3. 安裝 Scriptable

  4. 安裝後先打開一次

Scriptable 是一個讓使用者在 iOS 執行 JavaScript 自動化的工具,並支援建立主畫面 Widget。其 JavaScript 環境使用 Apple JavaScriptCore。

可以把它簡單理解成:一個讓你把 JavaScript 放到 iPhone 執行,並把結果顯示成 Widget 的工具。

Step 5|在 Scriptable 建立新 Script

打開 Scriptable 後:

  1. 按右上角的 +

  2. 建立新的 Script

  3. 將名稱改為:

KMB-Favourites-Widget
KMB-Favourites-Widget
KMB-Favourites-Widget

名稱可以自行修改,但教學時建議大家使用相同名稱。

這樣在之後設定 Widget 時,較不容易選錯 Script。


Step 6|貼上 Codex 生成的程式碼

進入剛才建立的 Script:

  1. 刪除原有示例內容

  2. 返回 Codex

  3. 複製完整 JavaScript

  4. 貼入 Scriptable

  5. 按右上角 Done 儲存

這裡最重要的是:一定要由第一行複製到最後一行。

常見錯誤包括:

  • 只複製了中間一部分

  • 漏掉第一個函式

  • 漏掉最後幾行

  • 把 Markdown 的三個反引號一同貼入

  • 把英文引號改成全形中文引號

  • API 網址在複製時被切斷

  • 自行刪除了不明白的括號

如果出現錯誤,不要只對 Codex 說:不能用。
應該提供:

  • 完整錯誤訊息

  • 錯誤出現在哪一步

  • Scriptable 顯示的行數

  • 當時使用的 Widget Parameter

  • 預期結果

  • 實際結果

例如:

我把程式貼到 Scriptable 後,
執行時出現以下錯誤:

TypeError: Cannot read properties of undefined

錯誤顯示在第 86 行。

請先解釋可能原因,
再提供最小修改方法。
不要重新改寫整個程式。
我把程式貼到 Scriptable 後,
執行時出現以下錯誤:

TypeError: Cannot read properties of undefined

錯誤顯示在第 86 行。

請先解釋可能原因,
再提供最小修改方法。
不要重新改寫整個程式。
我把程式貼到 Scriptable 後,
執行時出現以下錯誤:

TypeError: Cannot read properties of undefined

錯誤顯示在第 86 行。

請先解釋可能原因,
再提供最小修改方法。
不要重新改寫整個程式。

這樣 Codex 才可以針對問題修改,而不是每次重新生成另一份完全不同的程式。


Step 7|先在 Scriptable 內直接執行

儲存後,不要立即把 Widget 加到主畫面。先在 Scriptable 內直接執行一次,確認:

  • 程式沒有語法錯誤

  • API 可以正常連線

  • 路線及方向正確

  • 上車站正確

  • 到站時間合理

  • 最近三班依時間排列

  • 更新時間有顯示

  • 中型 Widget 預覽清晰

  • 沒有班次時有適當提示

Scriptable 的 Widget 通常以 ListWidget 建立,再交由 Script.setWidget() 顯示於主畫面。官方亦提醒,Widget 存在記憶體限制;使用過多記憶體可能令 Widget 無法正常渲染。

因此,第一版應盡量保持簡單。

如果固定路線版本仍然未能穩定運作,就不要急着加入收藏或其他交通工具。


Step 8|把固定路線升級成收藏版本

固定顯示 269C,只能處理一個特定情境。

如果希望將它變成真正適合不同使用者的小工具,就需要加入「收藏」。

可以要求 Codex:

現在請把固定路線版本升級成收藏版本。

使用者需要可以:

1. 新增路線收藏
2. 新增車站收藏
3. 選擇方向
4. 選擇上車站
5. 自訂收藏名稱
6. 刪除收藏
7. 重新命名收藏
8. 預覽收藏
9. 使用 Widget Parameter 1、2、3,
   讓多個 Widget 分別顯示不同收藏

請保留目前已經正常運作的 ETA 查詢邏輯。

請先提出:
- 收藏資料結構
- 使用者操作流程
- 本機儲存方式
- 需要新增的函式

確認後才修改程式。
現在請把固定路線版本升級成收藏版本。

使用者需要可以:

1. 新增路線收藏
2. 新增車站收藏
3. 選擇方向
4. 選擇上車站
5. 自訂收藏名稱
6. 刪除收藏
7. 重新命名收藏
8. 預覽收藏
9. 使用 Widget Parameter 1、2、3,
   讓多個 Widget 分別顯示不同收藏

請保留目前已經正常運作的 ETA 查詢邏輯。

請先提出:
- 收藏資料結構
- 使用者操作流程
- 本機儲存方式
- 需要新增的函式

確認後才修改程式。
現在請把固定路線版本升級成收藏版本。

使用者需要可以:

1. 新增路線收藏
2. 新增車站收藏
3. 選擇方向
4. 選擇上車站
5. 自訂收藏名稱
6. 刪除收藏
7. 重新命名收藏
8. 預覽收藏
9. 使用 Widget Parameter 1、2、3,
   讓多個 Widget 分別顯示不同收藏

請保留目前已經正常運作的 ETA 查詢邏輯。

請先提出:
- 收藏資料結構
- 使用者操作流程
- 本機儲存方式
- 需要新增的函式

確認後才修改程式。

收藏並不是一張圖片,而是一組需要保存的設定資料。

例如:

收藏名稱:返工
交通工具:九巴
路線:269C
方向:往觀塘碼頭
上車站:天頌苑
收藏名稱:返工
交通工具:九巴
路線:269C
方向:往觀塘碼頭
上車站:天頌苑
收藏名稱:返工
交通工具:九巴
路線:269C
方向:往觀塘碼頭
上車站:天頌苑

Scriptable 的 FileManager 可以在裝置本機讀寫檔案,因此可以利用 JSON 檔案保存收藏及 Widget 選擇。

升級後,使用者執行 Script 時,可以看到「我的交通收藏」管理選單,例如:

  • 我的收藏

  • 新增路線收藏

  • 新增港鐵收藏

  • 新增輕鐵收藏

  • 編輯收藏

  • 刪除收藏

  • Widget 編號設定

  • 預覽 Widget


Step 9|新增第一個路線收藏

以 269C 為例:

  1. 打開「我的交通收藏」

  2. 選擇 +新增路線收藏

  3. 輸入 269C

  4. 選擇方向,例如 往觀塘碼頭

  5. 選擇上車站,例如 天頌苑

  6. 輸入收藏名稱,例如 返工

  7. 儲存

這裡需要特別分清兩個概念:

目的地/方向
= 這班車正在前往哪個方向

上車站
= 你現時在哪一個車站等車
目的地/方向
= 這班車正在前往哪個方向

上車站
= 你現時在哪一個車站等車
目的地/方向
= 這班車正在前往哪個方向

上車站
= 你現時在哪一個車站等車

因此,Widget 上不要只顯示一個模糊的「車站」標籤。

更清楚的寫法是:

上車站:天頌苑
方向:往觀塘碼頭
上車站:天頌苑
方向:往觀塘碼頭
上車站:天頌苑
方向:往觀塘碼頭

如果同一路線的去程和回程均經過名稱相近的車站,也應該把方向、服務類型及車站識別碼一同保存,避免選錯資料。


Step 10|把 Scriptable Widget 加到主畫面

收藏建立完成後,便可以將 Widget 放到 iPhone 主畫面。

  1. 返回 iPhone 主畫面

  2. 長按空白位置

  3. 選擇加入小工具

  4. 搜尋 Scriptable

  5. 選擇中型 Widget

  6. 加入主畫面

今次的版面主要為中型 Widget 設計,因為它有較足夠空間顯示:

  • 收藏名稱

  • 路線

  • 上車站

  • 方向

  • 三班到站時間

  • 更新時間

小型 Widget 空間較少;大型 Widget 則需要另一套資訊層級和版面,因此不列入第一版教學重點。

Step 11|設定 Script 和 Widget Parameter

加入 Widget 後,還需要告訴它應該執行哪一個 Script。

  1. 長按剛加入的 Widget

  2. 選擇 編輯小工具

  3. 在 Script 選擇:KMB-Favourites-Widget

  1. 在 Widget Parameter 輸入:1

Scriptable 官方文件指出,主畫面 Widget 可以設定 Parameter,程式則透過 args.widgetParameter 讀取。這個參數可以用來區分多個 Widget 的行為。

Step 12|理解 Widget Parameter

這是整個教學最容易混淆的地方之一:Widget Parameter 不是路線號碼。

在今次設計中,Widget Parameter 代表「Widget 編號」。


例如:

輸入 1
= 顯示分配給 Widget 1 的收藏

輸入 2
= 顯示分配給 Widget 2 的收藏

輸入 3
= 顯示分配給 Widget 3 的收藏
輸入 1
= 顯示分配給 Widget 1 的收藏

輸入 2
= 顯示分配給 Widget 2 的收藏

輸入 3
= 顯示分配給 Widget 3 的收藏
輸入 1
= 顯示分配給 Widget 1 的收藏

輸入 2
= 顯示分配給 Widget 2 的收藏

輸入 3
= 顯示分配給 Widget 3 的收藏

假設你想同時查看兩條路線,可以在主畫面加入兩個 Scriptable 中型 Widget:

第一個 Widget
Parameter:1
收藏:返工

第二個 Widget
Parameter:2
收藏:回家
第一個 Widget
Parameter:1
收藏:返工

第二個 Widget
Parameter:2
收藏:回家
第一個 Widget
Parameter:1
收藏:返工

第二個 Widget
Parameter:2
收藏:回家

兩個 Widget 使用同一個 Script,但根據不同 Parameter 顯示不同收藏。

這也讓學員理解一個重要概念:

不一定要複製多份程式,才可以顯示不同內容;同一套程式也可以透過參數改變行為。

Step 13|點擊 Widget 切換收藏

之後如果想更換路線,不需要重新貼入程式碼。

理想操作流程是:

  1. 點擊主畫面上的 Widget

  2. Scriptable 打開對應的收藏清單

  3. 程式識別目前 Widget 的編號

  4. 使用者選擇另一個收藏

  5. 程式保存新的選擇

  6. Scriptable 顯示更新後的預覽

Scriptable 支援以 URL Scheme 打開 App 或指定 Script,因此可以把 Widget 的點擊行為連接到收藏管理流程。

不過,程式需要自行處理:

  • 目前是否在 Widget 模式執行

  • 目前是否在 Scriptable App 內執行

  • Widget Parameter 是甚麼

  • 點擊後應該打開哪個操作

  • 新收藏應該保存到哪個 Widget 編號

不要假設 Scriptable 會自動知道使用者希望更換哪一個收藏。

Step 14|逐步加入更多交通工具

當九巴固定路線及收藏版本穩定後,才逐步加入:

  • 龍運

  • 新大嶼山巴士

  • 綠色專線小巴

  • 港鐵

  • 輕鐵

可以要求 Codex:

請在現有收藏系統上,
逐步加入以下交通工具:

1. 龍運
2. 新大嶼山巴士
3. 綠色專線小巴
4. 港鐵
5. 輕鐵

第一版不需要紅色小巴。

要求:

- 每次只加入一種交通工具
- 加入前先閱讀官方數據文件
- 列出所需參數
- 列出會使用的回傳欄位
- 列出空白或錯誤情況
- 不要猜測 API 網址或欄位
- 不要破壞已經正常運作的交通工具
請在現有收藏系統上,
逐步加入以下交通工具:

1. 龍運
2. 新大嶼山巴士
3. 綠色專線小巴
4. 港鐵
5. 輕鐵

第一版不需要紅色小巴。

要求:

- 每次只加入一種交通工具
- 加入前先閱讀官方數據文件
- 列出所需參數
- 列出會使用的回傳欄位
- 列出空白或錯誤情況
- 不要猜測 API 網址或欄位
- 不要破壞已經正常運作的交通工具
請在現有收藏系統上,
逐步加入以下交通工具:

1. 龍運
2. 新大嶼山巴士
3. 綠色專線小巴
4. 港鐵
5. 輕鐵

第一版不需要紅色小巴。

要求:

- 每次只加入一種交通工具
- 加入前先閱讀官方數據文件
- 列出所需參數
- 列出會使用的回傳欄位
- 列出空白或錯誤情況
- 不要猜測 API 網址或欄位
- 不要破壞已經正常運作的交通工具

不同交通工具的資料,就像由不同公司提供的表格。

有些使用:

  • 路線編號

  • 車站 ID

  • 方向

  • 服務類型

  • ETA 時間

另一些可能使用:

  • 路線代碼

  • 車站代碼

  • 月台

  • 上行或下行

  • 終點站

  • 分鐘文字

因此,不能把一間公司的解析方式直接套用到另一間公司。


將不同交通資料轉成統一格式

雖然 API 格式不同,但 Widget 最後需要的資料大致相同。

可以要求 Codex 將每種交通工具的資料,統一轉換成:

{
  operator,
  route,
  stopName,
  destination,
  arrivals,
  updatedAt,
  status
}
{
  operator,
  route,
  stopName,
  destination,
  arrivals,
  updatedAt,
  status
}
{
  operator,
  route,
  stopName,
  destination,
  arrivals,
  updatedAt,
  status
}

例如:

operator
= 九巴、龍運、港鐵或輕鐵

route
= 269C、屯馬綫或輕鐵路線

stopName
= 使用者的上車站

destination
= 行車方向或終點站

arrivals
= 最近幾班車的到站資料

updatedAt
= 資料更新時間

status
= 正常、沒有班次或暫時無法讀取
operator
= 九巴、龍運、港鐵或輕鐵

route
= 269C、屯馬綫或輕鐵路線

stopName
= 使用者的上車站

destination
= 行車方向或終點站

arrivals
= 最近幾班車的到站資料

updatedAt
= 資料更新時間

status
= 正常、沒有班次或暫時無法讀取
operator
= 九巴、龍運、港鐵或輕鐵

route
= 269C、屯馬綫或輕鐵路線

stopName
= 使用者的上車站

destination
= 行車方向或終點站

arrivals
= 最近幾班車的到站資料

updatedAt
= 資料更新時間

status
= 正常、沒有班次或暫時無法讀取

可以向 Codex 提出:

請為每種交通工具建立獨立的資料讀取函式。

但所有函式最後都必須回傳以下統一格式:

{
  operator,
  route,
  stopName,
  destination,
  arrivals,
  updatedAt,
  status
}

Widget UI 不應直接處理各交通公司的原始 API 格式。

API 的差異應該由資料處理層負責,
Widget 只負責顯示統一資料。
請為每種交通工具建立獨立的資料讀取函式。

但所有函式最後都必須回傳以下統一格式:

{
  operator,
  route,
  stopName,
  destination,
  arrivals,
  updatedAt,
  status
}

Widget UI 不應直接處理各交通公司的原始 API 格式。

API 的差異應該由資料處理層負責,
Widget 只負責顯示統一資料。
請為每種交通工具建立獨立的資料讀取函式。

但所有函式最後都必須回傳以下統一格式:

{
  operator,
  route,
  stopName,
  destination,
  arrivals,
  updatedAt,
  status
}

Widget UI 不應直接處理各交通公司的原始 API 格式。

API 的差異應該由資料處理層負責,
Widget 只負責顯示統一資料。

這個設計可以避免每加入一種交通工具,就要重新改寫整個 Widget UI。


Step 15|請 Codex 設計測試

Vibe Coding 不是讓 AI 生成程式後便直接使用。

我們仍然需要測試,確保程式遇到特殊情況時,不會立即停止運作。

可以要求 Codex:

請為這個 Scriptable 香港交通到站時間 Widget
設計至少 12 個測試情況。

必須包括:

1. 第一次使用,沒有任何收藏
2. 新增正常路線收藏
3. 新增車站收藏
4. 刪除收藏
5. 重新命名收藏
6. Widget Parameter 沒有填寫
7. 多個 Widget 使用不同編號
8. API 暫時沒有班次
9. 網絡連線失敗
10. API 回傳格式錯誤或缺少欄位
11. 出現同名車站
12. 港鐵、輕鐵及小巴格式不同

請為每個測試列出:

- 測試目的
- 測試步驟
- 預期結果
- 失敗時應顯示的訊息
- 涉及的程式位置

如果你能在目前環境真正執行測試,
請提供實際測試輸出。

如果不能執行,
請清楚標示為「建議測試案例」,
不要聲稱測試已經通過。
請為這個 Scriptable 香港交通到站時間 Widget
設計至少 12 個測試情況。

必須包括:

1. 第一次使用,沒有任何收藏
2. 新增正常路線收藏
3. 新增車站收藏
4. 刪除收藏
5. 重新命名收藏
6. Widget Parameter 沒有填寫
7. 多個 Widget 使用不同編號
8. API 暫時沒有班次
9. 網絡連線失敗
10. API 回傳格式錯誤或缺少欄位
11. 出現同名車站
12. 港鐵、輕鐵及小巴格式不同

請為每個測試列出:

- 測試目的
- 測試步驟
- 預期結果
- 失敗時應顯示的訊息
- 涉及的程式位置

如果你能在目前環境真正執行測試,
請提供實際測試輸出。

如果不能執行,
請清楚標示為「建議測試案例」,
不要聲稱測試已經通過。
請為這個 Scriptable 香港交通到站時間 Widget
設計至少 12 個測試情況。

必須包括:

1. 第一次使用,沒有任何收藏
2. 新增正常路線收藏
3. 新增車站收藏
4. 刪除收藏
5. 重新命名收藏
6. Widget Parameter 沒有填寫
7. 多個 Widget 使用不同編號
8. API 暫時沒有班次
9. 網絡連線失敗
10. API 回傳格式錯誤或缺少欄位
11. 出現同名車站
12. 港鐵、輕鐵及小巴格式不同

請為每個測試列出:

- 測試目的
- 測試步驟
- 預期結果
- 失敗時應顯示的訊息
- 涉及的程式位置

如果你能在目前環境真正執行測試,
請提供實際測試輸出。

如果不能執行,
請清楚標示為「建議測試案例」,
不要聲稱測試已經通過。

最後一項非常重要。

AI 可以協助設計測試,但不代表它一定已經在真實 iPhone、真實 Scriptable 環境及所有交通 API 上完成測試。

因此,需要分清:測試案例已經列出和測試已經在真實環境執行並通過兩


為普通使用者設計錯誤訊息

以下訊息適合開發者查看,但不適合直接顯示在 Widget:

TypeError: undefined is not an object
TypeError: undefined is not an object
TypeError: undefined is not an object
JSON Parse Error
JSON Parse Error
JSON Parse Error
Request failed with status 500
Request failed with status 500
Request failed with status 500

Widget 應改為顯示:

暫時無法讀取到站資料
暫時無法讀取到站資料
暫時無法讀取到站資料
目前未有班次資料
目前未有班次資料
目前未有班次資料
收藏資料需要重新設定
收藏資料需要重新設定
收藏資料需要重新設定
請打開 Scriptable 再試一次
請打開 Scriptable 再試一次
請打開 Scriptable 再試一次

但同時,程式可以在 Scriptable Console 保留完整技術錯誤,方便之後交給 Codex 分析。

可以要求:

請把錯誤處理分成兩層:

第一層:
Widget 顯示普通使用者能理解的訊息。

第二層:
Scriptable Console 保留完整技術錯誤,
包括 API 狀態、錯誤類型及相關函式。

不要把 TypeError、JSON Parse Error
或完整 API 回應直接顯示在 Widget。
請把錯誤處理分成兩層:

第一層:
Widget 顯示普通使用者能理解的訊息。

第二層:
Scriptable Console 保留完整技術錯誤,
包括 API 狀態、錯誤類型及相關函式。

不要把 TypeError、JSON Parse Error
或完整 API 回應直接顯示在 Widget。
請把錯誤處理分成兩層:

第一層:
Widget 顯示普通使用者能理解的訊息。

第二層:
Scriptable Console 保留完整技術錯誤,
包括 API 狀態、錯誤類型及相關函式。

不要把 TypeError、JSON Parse Error
或完整 API 回應直接顯示在 Widget。

Step 16|理解 iPhone Widget 的自動更新限制

交通營辦商的數據可能經常更新,但 iPhone 主畫面 Widget 何時重新執行,並不是 Scriptable 程式可以完全控制。

Scriptable 官方文件指出,Widget 會定期刷新,但實際刷新頻率主要由作業系統決定。Apple 的 WidgetKit 亦以 Timeline 及刷新政策管理 Widget 更新,由系統在合適時間重新載入內容。

正確的說法是:

程式可以向系統提出稍後更新的要求,
但不能保證主畫面 Widget 每分鐘準時刷新。
程式可以向系統提出稍後更新的要求,
但不能保證主畫面 Widget 每分鐘準時刷新。
程式可以向系統提出稍後更新的要求,
但不能保證主畫面 Widget 每分鐘準時刷新。

因此,Widget 必須顯示最近一次更新時間,例如:

更新:08:42
更新:08:42
更新:08:42

如果使用者希望立即取得最新資料,可以:

  1. 點擊 Widget

  2. 打開 Scriptable

  3. 重新執行 Script

  4. 查看最新預覽

這不是程式一定出現故障,而是 iOS Widget 的系統限制。


不要把個人 Widget 當成絕對即時工具

這個項目適合:

  • 日常快速參考

  • 個人常用路線

  • API 練習

  • JavaScript 練習

  • Vibe Coding 教學

  • Widget 產品原型

但不應假設它可以百分之百取代營辦商或官方交通渠道。

到站資料可能受到以下因素影響:

  • 網絡連線

  • API 暫停

  • API 欄位改動

  • 班次取消

  • 特別交通安排

  • 改道

  • Widget 未及刷新

  • 收藏資料已經過時

如果涉及重要行程,例如趕飛機、考試或重要會議,仍應返回營辦商或官方渠道核對最新資訊。

完整教學流程總結

整條學習路線可以整理成:

生活問題
寫清楚 Prompt
Codex 拆解規格
確認官方資料來源
製作固定路線 MVP
把程式貼入 Scriptable
在手機內預覽
加入收藏功能
設定 Widget Parameter
加入多個 Widget
逐一接駁其他交通工具
統一不同 API 格式
使用 Codex 設計測試
處理錯誤及刷新限制
放到 iPhone 主畫面使用
生活問題
寫清楚 Prompt
Codex 拆解規格
確認官方資料來源
製作固定路線 MVP
把程式貼入 Scriptable
在手機內預覽
加入收藏功能
設定 Widget Parameter
加入多個 Widget
逐一接駁其他交通工具
統一不同 API 格式
使用 Codex 設計測試
處理錯誤及刷新限制
放到 iPhone 主畫面使用
生活問題
寫清楚 Prompt
Codex 拆解規格
確認官方資料來源
製作固定路線 MVP
把程式貼入 Scriptable
在手機內預覽
加入收藏功能
設定 Widget Parameter
加入多個 Widget
逐一接駁其他交通工具
統一不同 API 格式
使用 Codex 設計測試
處理錯誤及刷新限制
放到 iPhone 主畫面使用

學員真正需要掌握的,並不是背下某一段 JavaScript。

更重要的是這套方法:

先把需求說清楚,

再把大型功能拆成細小步驟。

每完成一步便立即驗證,

出錯時只修改有問題的部分。

最後才逐步增加收藏、

交通工具及進階功能。

給學員的完整練習 Prompt(可複製)

我想製作一個免費的 iPhone 主畫面 Widget,
用來顯示香港交通工具的預計到站時間。

我不是工程師,不想使用 Xcode,
希望利用 Codex 協助開發,
並在 Scriptable 執行 JavaScript。

請按照以下階段協助我。

第一階段:先寫規格,不要寫程式

請列出:

1. 第一版必做功能
2. 之後才加入的功能
3. 使用者操作流程
4. 所需官方資料來源
5. 收藏資料結構
6. 需要測試的情況
7. 已知限制
8. 第一版完成標準

第二階段:製作最小可行版本

固定一條路線、一個上車站及一個方向,
顯示最近三班預計到站時間及更新時間。

請先解釋:

- API 查詢流程
- 所需識別碼
- 主要回傳欄位
- 程式結構
- 錯誤處理方法

然後才提供可以貼入 Scriptable 的完整 JavaScript。

第三階段:加入收藏功能

使用者可以:

- 新增路線收藏
- 新增車站收藏
- 選擇方向
- 選擇上車站
- 自訂收藏名稱
- 刪除收藏
- 重新命名收藏
- 預覽收藏

每個 Widget 可以利用
Widget Parameter 1、2、3
分別顯示不同收藏。

Widget Parameter Widget 編號,
不是交通路線號碼。

第四階段:加入其他交通工具

請逐一加入:

- 九巴
- 龍運
- 新大嶼山巴士
- 綠色專線小巴
- 港鐵
- 輕鐵

第一版不需要紅色小巴。

每加入一種交通工具前,
必須先閱讀可核對的官方數據文件。

不要猜測 API 網址、參數或欄位。

請把不同 API 的資料統一轉換成:

{
  operator,
  route,
  stopName,
  destination,
  arrivals,
  updatedAt,
  status
}

第五階段:測試

請設計至少 12 個測試情況,包括:

- 沒有收藏
- 新增收藏
- 刪除收藏
- Widget Parameter 留空
- 多個 Widget 編號
- 沒有班次
- 網絡失敗
- API 格式錯誤
- 缺少欄位
- 同名車站
- 收藏檔案損壞
- 不同交通工具格式

請清楚區分:

- 建議測試案例
- 已經真正執行的測試
- 實際通過或失敗的結果

不要在沒有執行測試時,
聲稱所有測試已經通過。

普通使用者在 Widget 上不應看到
TypeError、JSON Parse Error
或其他技術錯誤。

請每次只完成一個階段。

完成後先讓我測試,
不要一次過加入全部功能。
我想製作一個免費的 iPhone 主畫面 Widget,
用來顯示香港交通工具的預計到站時間。

我不是工程師,不想使用 Xcode,
希望利用 Codex 協助開發,
並在 Scriptable 執行 JavaScript。

請按照以下階段協助我。

第一階段:先寫規格,不要寫程式

請列出:

1. 第一版必做功能
2. 之後才加入的功能
3. 使用者操作流程
4. 所需官方資料來源
5. 收藏資料結構
6. 需要測試的情況
7. 已知限制
8. 第一版完成標準

第二階段:製作最小可行版本

固定一條路線、一個上車站及一個方向,
顯示最近三班預計到站時間及更新時間。

請先解釋:

- API 查詢流程
- 所需識別碼
- 主要回傳欄位
- 程式結構
- 錯誤處理方法

然後才提供可以貼入 Scriptable 的完整 JavaScript。

第三階段:加入收藏功能

使用者可以:

- 新增路線收藏
- 新增車站收藏
- 選擇方向
- 選擇上車站
- 自訂收藏名稱
- 刪除收藏
- 重新命名收藏
- 預覽收藏

每個 Widget 可以利用
Widget Parameter 1、2、3
分別顯示不同收藏。

Widget Parameter Widget 編號,
不是交通路線號碼。

第四階段:加入其他交通工具

請逐一加入:

- 九巴
- 龍運
- 新大嶼山巴士
- 綠色專線小巴
- 港鐵
- 輕鐵

第一版不需要紅色小巴。

每加入一種交通工具前,
必須先閱讀可核對的官方數據文件。

不要猜測 API 網址、參數或欄位。

請把不同 API 的資料統一轉換成:

{
  operator,
  route,
  stopName,
  destination,
  arrivals,
  updatedAt,
  status
}

第五階段:測試

請設計至少 12 個測試情況,包括:

- 沒有收藏
- 新增收藏
- 刪除收藏
- Widget Parameter 留空
- 多個 Widget 編號
- 沒有班次
- 網絡失敗
- API 格式錯誤
- 缺少欄位
- 同名車站
- 收藏檔案損壞
- 不同交通工具格式

請清楚區分:

- 建議測試案例
- 已經真正執行的測試
- 實際通過或失敗的結果

不要在沒有執行測試時,
聲稱所有測試已經通過。

普通使用者在 Widget 上不應看到
TypeError、JSON Parse Error
或其他技術錯誤。

請每次只完成一個階段。

完成後先讓我測試,
不要一次過加入全部功能。
我想製作一個免費的 iPhone 主畫面 Widget,
用來顯示香港交通工具的預計到站時間。

我不是工程師,不想使用 Xcode,
希望利用 Codex 協助開發,
並在 Scriptable 執行 JavaScript。

請按照以下階段協助我。

第一階段:先寫規格,不要寫程式

請列出:

1. 第一版必做功能
2. 之後才加入的功能
3. 使用者操作流程
4. 所需官方資料來源
5. 收藏資料結構
6. 需要測試的情況
7. 已知限制
8. 第一版完成標準

第二階段:製作最小可行版本

固定一條路線、一個上車站及一個方向,
顯示最近三班預計到站時間及更新時間。

請先解釋:

- API 查詢流程
- 所需識別碼
- 主要回傳欄位
- 程式結構
- 錯誤處理方法

然後才提供可以貼入 Scriptable 的完整 JavaScript。

第三階段:加入收藏功能

使用者可以:

- 新增路線收藏
- 新增車站收藏
- 選擇方向
- 選擇上車站
- 自訂收藏名稱
- 刪除收藏
- 重新命名收藏
- 預覽收藏

每個 Widget 可以利用
Widget Parameter 1、2、3
分別顯示不同收藏。

Widget Parameter Widget 編號,
不是交通路線號碼。

第四階段:加入其他交通工具

請逐一加入:

- 九巴
- 龍運
- 新大嶼山巴士
- 綠色專線小巴
- 港鐵
- 輕鐵

第一版不需要紅色小巴。

每加入一種交通工具前,
必須先閱讀可核對的官方數據文件。

不要猜測 API 網址、參數或欄位。

請把不同 API 的資料統一轉換成:

{
  operator,
  route,
  stopName,
  destination,
  arrivals,
  updatedAt,
  status
}

第五階段:測試

請設計至少 12 個測試情況,包括:

- 沒有收藏
- 新增收藏
- 刪除收藏
- Widget Parameter 留空
- 多個 Widget 編號
- 沒有班次
- 網絡失敗
- API 格式錯誤
- 缺少欄位
- 同名車站
- 收藏檔案損壞
- 不同交通工具格式

請清楚區分:

- 建議測試案例
- 已經真正執行的測試
- 實際通過或失敗的結果

不要在沒有執行測試時,
聲稱所有測試已經通過。

普通使用者在 Widget 上不應看到
TypeError、JSON Parse Error
或其他技術錯誤。

請每次只完成一個階段。

完成後先讓我測試,
不要一次過加入全部功能。

DotAI 獨家點評

從 DotAI 的角度來看,這個項目最值得學習的,不只是一個交通 Widget,而是完整經歷一次「由生活問題到可用產品」的流程。

1|Vibe Coding 不是一句 Prompt 生成完整 App

AI 可以協助寫程式,但使用者仍然需要負責:

  • 定義問題

  • 決定第一版範圍

  • 選擇可靠資料來源

  • 檢查 AI 輸出

  • 測試特殊情況

  • 判斷哪些功能應該延後

真正有效的 AI 協作,是由人設定方向、逐步驗證,再讓 AI 加快執行。

2|先完成一條路線,比同時支援所有交通工具更重要

如果固定路線版本仍然未能穩定顯示,立即加入六種交通工具,只會令問題更難追查。

第一版應該先證明:

  • API 能夠讀取

  • 車站配對正確

  • 時間計算合理

  • Widget 可以顯示

  • 錯誤有清楚提示

之後才加入收藏及其他交通工具。

3|官方數據文件也是產品的一部分

AI 能夠生成程式,不代表它一定知道每個交通 API 的正確格式。

每加入一個資料來源,都需要確認:

  • API 網址

  • 必填參數

  • 路線和車站識別碼

  • 更新頻率

  • 回傳欄位

  • 空白狀態

  • 錯誤格式

  • 使用限制

不要看到 API 有回應,便假設資料一定配對正確。

4|統一資料格式比堆疊條件更重要

不同交通 API 應該由不同函式處理,但最後回傳相同格式。

這樣 Widget UI 才不需要知道:

  • 九巴如何命名方向

  • 港鐵如何表示上行和下行

  • 輕鐵如何表示月台

  • 小巴如何回傳班次

資料層處理差異,畫面層只負責顯示,之後會較容易維護。

5|Widget Parameter 是很好的產品設計練習

同一個 Script 可以支援多個 Widget,只需要讓不同 Widget 使用不同 Parameter。

這讓學員理解:

功能不一定要靠複製程式實現,也可以利用參數控制同一套程式。

6|測試結果不能由 AI 自行宣佈

Codex 可以設計測試、執行部分測試及分析錯誤,但必須確認測試是否真的在相關環境執行。

尤其 Scriptable、iOS Widget 及真實交通 API 的行為,不一定可以由一般桌面 JavaScript 測試完全覆蓋。

因此,需要清楚分開:

  • 程式邏輯測試

  • 模擬 API 測試

  • Scriptable 實機測試

  • iPhone 主畫面測試

  • 真實交通數據測試

7|知道系統限制,也是產品能力的一部分

iOS 不保證 Widget 每分鐘刷新,因此設計時必須:

  • 顯示更新時間

  • 接受數據可能稍舊

  • 提供手動查看方式

  • 不把畫面描述成絕對即時

知道工具做不到甚麼,和知道工具做到甚麼同樣重要。


總結

如果你想利用 Codex+Scriptable 製作香港交通到站時間 Widget,可以先記住以下重點:

  • 先說清楚生活問題,不要只叫 AI「做一個 App」。

  • 先要求 Codex 寫規格,再開始生成程式。

  • 第一版只做一條固定路線、一個車站及最近三班車。

  • 每個交通工具都應使用可核對的官方資料來源。

  • 路線名稱和車站名稱不一定等於 API 識別碼。

  • 收藏可以使用 Scriptable 本機檔案保存。

  • Widget Parameter 是 Widget 編號,不是路線號碼。

  • 不同交通 API 應先轉成統一資料格式。

  • 每加入一項功能,都應立即測試。

  • 建議測試案例不等於測試已經通過。

  • 技術錯誤應保留在 Console,而不是直接顯示給使用者。

  • iPhone Widget 的刷新由 iOS 管理,不能保證每分鐘更新。

  • 這個 Widget 適合日常參考,不能完全取代官方交通渠道。

這個項目最適合新手的地方,是它不是一個遙遠而龐大的完整 App。

它由一個很簡單的生活問題開始:

可不可以不用每天重新搜尋,直接在手機主畫面看到常用路線的到站時間?

然後一步一步變成:

需求
規格
官方數據
程式
Widget
收藏
測試
日常工具
需求
規格
官方數據
程式
Widget
收藏
測試
日常工具
需求
規格
官方數據
程式
Widget
收藏
測試
日常工具

這亦是 Vibe Coding 真正值得學習的地方。

不是要求 AI 一次生成完美產品,而是學會如何清楚描述、逐步實現、持續驗證,再把一個生活想法變成自己真正用得到的小工具。

 DotAI Spot
AI 實戰成長社群

DotAI 全新學習體驗,陪伴您跨越 AI 學習迷惘

Mobile Image
Mobile UI
Camera
Mobile Image
Mobile UI
Camera
Mobile Image
Camera

其他文章

想了解如何通過 AI 增強競爭優勢?

隨時歡迎聯絡我們!

地址

Room 901 , Gravity,  29, Hing Yip Street,

Kwun Tong, Kowloon

追蹤我們的社交媒體

留下訊息

我們會儘快回應你的訊息

想了解如何通過 AI 增強競爭優勢?

隨時歡迎聯絡我們!

地址

Room 901 , Gravity,  29, Hing Yip Street,

Kwun Tong, Kowloon

追蹤我們的社交媒體

留下訊息

我們會儘快回應你的訊息

致力推動香港 AI 發展

© Animo technology limited 2026. All Rights Reserved

致力推動香港 AI 發展

© Animo technology limited 2026. All Rights Reserved