小島神話記錄島嶼的眾神諸佛

開發日誌

記錄小島神話的開發歷程:功能上線、內容里程碑、資料模型演進,也包含踩雷與撤回。每則附工程細節可展開。

  1. 資料工程

    「代天巡狩」把五間廟歸錯了神——別名衝突從警告改成擋 build

    站上每間廟的主祀神是靠名稱比對自動歸戶的,比對表除了神明本名,還收各地的別稱。問題出在有些稱號不專屬於任何一尊——「代天巡狩」是所有王爺共用的職稱,卻同時被寫進李府千歲與范府千歲兩篇的別名欄,程式只好用讀檔順序決定歸誰。結果台南五間登記主祀為「代天巡狩」的廟,全被無憑無據地掛到范府千歲名下,而且放了好幾週沒人發現——因為程式確實有警告,只是那行警告印在每天凌晨自動部署的紀錄裡,沒有人會去看。這天把規則翻過來:別名撞名不再是警告,而是直接讓建置失敗。代價是往後自動產出的新條目只要帶到共用稱號,當天的更新就會停擺;換到的是誤配不會再安靜地累積下去。

    工程細節

    站上 12,414 間廟的主祀神是自動歸戶的:ETL 把 src/content/deities/*.md 每篇的 name 與 titles[] 攤平成一張別名索引,再拿內政部寺廟登記快照裡的主祀欄位去查表。這張表從 P1a 就是這樣建的,一直運作得不錯——直到別名本身撞名。

    警告印了幾週,沒有人看

    規格(docs/specs/temple-registry.md)對衝突原本分三級處理:兩檔 name 相同視同資料損毀、exit 1;同檔把 name 又寫進 titles 靜默略過;別名撞上他檔的鍵,只 console.warn、不擋 build。

    最後那條當初是刻意寫的,理由寫在規格裡:ETL 跑在每天 04:30 的自治部署鏈上,為一個內容小疵停掉整條線代價太高。

    這個理由的前提是「有人會看到警告」。實際上不會。警告的落點是部署 log,而部署是無人值守的排程,成功就不會有人去翻。於是:

    • **「代天巡狩」**同時掛在 li-fu-qiansui(李府千歲)與 fan-fu-qiansui(范府千歲)的 titles。ETL 依讀檔順序先到先得,前端的 deity-resolve.ts 卻是後到覆蓋——兩邊的答案還不一定一致。台南五間登記主祀為「代天巡狩」的廟(蘇厝真護宮、基華宮、俊天宮、佳里代天宮、柳營代天院)就這樣被整批歸給范府千歲。
    • **「老爺」**同時掛在 xiqin-wangye(西秦王爺)與 tiandu-yuanshuai(田都元帥)。這個更明顯:戲班對北管西秦王爺與南管田都元帥都叫「老爺」,城隍老爺、福德老爺也都在用。當別名索引的鍵,必然誤配。

    兩者的共通點是:它們根本不是別名,是職稱。「代天巡狩」是王爺奉旨代天巡察的共通身分,不指特定哪一位;「老爺」是俗稱。把共用稱號寫進 titles[],等於向程式宣告「凡登記成這個名字的廟都算這一尊」,而這件事沒有任何來源支持。

    修法:兩邊都拿掉,不要讓讀檔順序決定

    兩篇的 titles 各自移除「代天巡狩」、兩篇移除「老爺」,內文保留——「范府千歲又稱代天巡狩」這句話在條目正文裡是對的,它只是不該成為比對鍵。

    副作用照實記:那五間廟改為未對應,全站主祀命中數從 10,166 降到 10,161。這是刻意的退步——少歸五間,好過無憑據地歸錯五間。日後查得各廟實際供奉的是哪一府,再逐廟用修正層指定。

    同批還清掉兩筆與內文自相矛盾的別名:dizang-wang-pusa 的「酆都大帝(民間混同)」、baogong 的「閻羅天子」。括號註記在建索引時會被剝掉,等於把「民間會混稱」硬化成「就是同一尊」——而這兩篇的正文都明寫了不是。

    決策翻轉:warn 改成 exit 1

    真正的修正不在資料而在規則。build-temple-registry.mjs 的 aliasClashes 非空時改 console.error + process.exit(1),錯誤訊息附上處置原則。規格裡原本那句「別名衝突不 fail 是刻意的」沒有刪,劃線保留並補上翻轉的日期與理由——決策改了要看得出來改過什麼。

    代價講清楚:自治管線產出的新條目若帶了衝突別名,會停掉當天的部署(TG 會通知 build 失敗)。這個代價是被接受的,因為另一邊的代價是誤配靜默累積數週。

    順手補進規格的處置原則:衝突鍵若是多尊共用的泛稱或職稱(代天巡狩、老爺、戲神這類),從雙方 titles 移除、寫在內文;只有確認屬於某一尊時才留在該檔。不要用「讓讀檔順序決定」的方式保留。驗證方式是暫時塞回一組衝突別名,確認會擋、再確認現行內容通過。

    同一天還有一件事:擋得住的,也擋了三天沒人管

    諷刺的是,就在改成 exit 1 的幾十分鐘前,才剛修好一個已經在擋的失敗。

    wan-fu-qiansui(萬府千歲)的 blessings 填了「海上平安」,不在受控詞彙的 14 項之內,ETL 驗證直接 exit 1——從 09-14 起連續三天,凌晨的自動部署都在 build 這一步失敗、中止上線。改成受控詞彙「行船漁獲」(與天上聖母、水仙尊王等海事神祇同用法)後,astro build 接著在 content collection 驗證失敗:zhao-fu-qiansui(趙府千歲)引用本站兩筆條目(趙公明、五年千歲)的 sources 缺必填的 grade,比照 lu-fu-qiansui 補上 grade: canonical 才過。

    所以同一天的兩件事其實指向同一個縫:擋 build 會讓問題浮上來,但浮上來不等於有人接住。硬紅線解決的是「不知道有問題」,不解決「知道了沒人處理」。前者現在補上了,後者仍然靠 TG 通知與人每天瞄一眼——這條還沒有自動化的答案。

  2. 內容

    這一批不約而同寫到宜蘭——抗日的鸞堂、戒鴉片的鸞堂,和一尊從創世神變成送子神的女媧

    五天之內站上多了 6 篇文章與 1 尊神祇條目,另有 11 篇既有條目完成再查核、6 尊補上神像圖,神祇條目從 138 增至 139、文章從 85 增至 91,合計 26 支自動產出的變更經人工終審後上線。沒有人指定主軸,但六篇文章裡有三篇寫到了同一個地方:宜蘭。一座 1895 年割台後由士紳扶鸞建起、後來成為全國第一座縣定古蹟的岳飛廟;兩個 1893 年跑去廣東學扶鸞、回來開堂幫人戒鴉片的宜蘭人;以及壯圍那間女媧廟——因為「補天」台語諧音「補添」,煉石補天的創世女神在這裡變成了求子神,又因為天穹像傘,造傘業者也來拜。另外兩篇是為日期寫的:九月廿五月老聖誕、九月廿八祭孔。

    工程細節

    九月十六日到二十日之間,站上合併了 26 支自治管線產出的變更(PR #262–#287):6 篇文章、1 尊神祇建檔、11 篇再查核、6 張神像圖,以及 2 篇開發日誌自己。神祇條目 138 → 139,文章 85 → 91。

    上一批(9/11–9/15)的共同點是「拆名單」。這一批的共同點不是題材,是地點。

    一、宜蘭,三次

    六篇文章裡有三篇的主場在宜蘭,而且彼此有關係。

    • 〈岳武穆王與宜蘭碧霞宮〉——全台主祀岳飛的登記寺廟只有五間,碧霞宮是最特別的一間。1895 年日本領台之初,宜蘭士紳延請新民堂扶鸞,得「宣揚忠孝,感化人心」的神諭,遂奉這位南宋名將為主神建廟;廟名一說取自「碧血丹心望曉霞」的隱語。1997 年它被指定為宜蘭縣第一座、也是全國第一座縣定古蹟。文章真正的重點不在香火而在制度:三百名門生分典儀、鸞務、宣講、禮誦、賑救、總務、財務七職,每月初一、十五晚間七到九點行「察點儀式」,恩主以鸞筆銜硃砂逐一唱名,門生應「在」,再於清卡落朱批——這套百年不輟的點名,比任何靈異敘事都更接近這間廟的本體。

    • 〈王靈官的堂口與鸞堂戒煙〉——全台主祀王靈官的十間廟,廟名有七間帶「堂」字:那不是道觀,是鸞堂。依衛福部國家中醫藥研究所建檔於國家文化記憶庫的資料,1893 年宜蘭人吳炳珠與莊國香赴廣東陸豐學鸞,返台開堂;兩年後他們與吳祥煇共同倡建頭城喚醒堂,主席神正是王天君。做法是:想戒的人一起在神前祈禱、待神明降鸞,服下混有香灰與淨砂的神水,最後宣誓停止吸食、毀壞用具。1897 年新竹樹杞林的彭殿華特請吳炳珠自頭城前往扶鸞戒煙,是鸞務外傳的開端。背景數字:1895 年割台時,全台吸食鴉片者約占總人口的 6.54%。 文章末尾特別加了一段分層說明——該筆建檔資料對此法有正面的效驗評價,那是建檔單位的評價而非本站立場;今日的鴉片類成癮屬醫療問題,應循正規醫療管道。這類「轉述來源的價值判斷」正是編輯守則要求切開的地方,照寫但標明歸屬。

    • 〈女媧在台灣〉——煉石補天的上古女神在台灣只有八間主祀廟,其中三間叫「補天宮」。宜蘭壯圍大福補天宮是縣內唯一一間女媧廟,而宜蘭縣政府文化局的著錄給了一個非常台灣的解釋:「補天」台語諧音「補添」,於是求姻緣、求生子的信眾不少;又因天穹如傘,造傘與紡織業者也來參拜,著錄的關鍵詞欄逕收「造傘業神」一詞。從煉石補天到補添子嗣、從撐起蒼穹到撐起一把傘——這是一個神話原型被地方讀解、長出新職能的完整案例。

    三篇並不是被排在一起寫的。它們各自來自不同的掃描訊號(主祀廟數缺口、既有條目延伸),前後兩天分別成題。會撞在一起,原因大概是清末到日治初期的宜蘭本來就是鸞堂密度最高的地方之一;而鸞堂這種「扶鸞—宣講—賑救」三合一的組織形態,剛好同時撐起了一間抗日心緒下的岳飛廟與一場戒鴉片運動。第四篇的西秦王爺文章裡也順帶出現宜蘭:羅東有一間直接名為「西秦爺廟」的。

    二、為了日期寫的兩篇

    九月六日那批開始的做法延續下來——先有日期,再反過來指定自治流程寫什麼。這次是兩個:

    • 九月廿五(中秋、月老聖誕):〈月老神君的出生證明〉回到唐傳奇〈定婚店〉的原典。教育部《成語典》完整收錄該篇全文,是國家辭書級、可直接查核的文本,而回到原典會看到三件與今日印象相反的事:那位老人自稱「幽吏」,是陰間的文書官而非慈祥媒人;赤繩的作用不是牽成而是「終不可逭」——逃不掉;而聽完預言的韋固,第一個反應是雇人去殺那名三歲女孩。
    • 九月廿八(祭孔):〈至聖先師的釋奠禮〉把臺北孔廟大成殿的三十七道儀節逐一拆開,從三通鼓、啟扉、瘞毛血到望燎、闔扉、禮成。其中一條值得記住的制度變動:古制的三跪九叩,是民國五十九年(1970)由內政部修訂公布改為三鞠躬的——今天在孔廟看到的鞠躬,只有五十多年歷史。

    三、再查核這一輪,翻到的還是別名

    11 篇再查核有個不算巧合的偏向:龍女、善財童子、千里眼·順風耳、牛頭馬面、十八羅漢、十殿閻羅——六篇都是配祀或群像,不是獨立受祀的主神。這類條目建檔時容易寫得單薄,lastVerified 又多半從未填過,掃描器據「從未查核」成題,自然一次撈出一串。

    比較值得記的是魁星那篇翻出的東西:原檔 titles 收「大魁夫子」,而寺廟登記實際寫的是「魁星夫子」——與華佗(華陀仙師/華佗先師)同型的近似寫法錯位。補上五個寫法並 grep 全站確認無跨檔衝突後,預計可多歸戶兩間廟。

    這件事和同一週那支擋 build 的改動(見〈「代天巡狩」把五間廟歸錯了神〉)剛好是一體兩面:別名寫太少會漏配,寫太寬會誤配。前者靠再查核一篇一篇撈回來,後者現在由建置階段硬擋。中間那條線——什麼算這一尊的別名、什麼只是共用稱號——仍然是編輯判斷,沒有程式能代勞。

    另外留下一個跨條目的未決項:星君類條目的位階層級不一致(太陽星君、太陰娘娘、南斗星君的道教視角一律不掛 tier,太歲星君卻掛到帝君層)。位階序列是本站的內容主張而非外部事實,再查核不逕改,兩篇都建議併同終審處理。

    四、圖像那頭定了一條新規則:無名者怎麼畫

    同期 6 張神像圖上線(斗姆元君、大將爺、地基主、明明上帝、普庵祖師、朱府千歲),但真正的進展是一條因為畫不出來而定下的規則。

    忠義公、有應公這類條目,研究查不到任何人形造像的可徵來源——埔心忠義廟的主神本體就是一方「神靈位」,CRGIS 的田野照片為證。排程的產圖任務無權自行解釋既有規則,依約升人工。這次定案走牌位構圖:畫面不出現任何人物、剪影、兵器、旗幟,只有一方直立神靈位置於供桌中央、上懸匾額意象。

    規則寫成常設條款,判準三條須同時成立:①稱號指涉一群無名或殉難者而非單一人格神;②查無任何人形造像的可徵來源;③可徵的奉祀形制為牌位、碑、祠等非人形。有可徵金身傳統者不適用——義民爺自 1951 年平鎮褒忠祠起就有金身,照一般規則研究。

    不畫人的理由寫進了規格:避免替無名者造形、避免立場暗示、避免鬼魅感。三條理由裡最實際的是第一條——那些人沒有留下名字,本站沒有立場替他們決定長相。

    附帶要修的是提示詞守則:原本的負面提示詞一律禁止「畫面提字」,而牌位的重點就是牌位上那幾個字,照舊會被自己的規則擋掉。改寫成禁止「牌位以外的提字落款」,並另加「人物」為禁項。

  3. 內容

    這一批都在拆名單——水仙尊王是哪五位、大聖不只齊天、月老只有一間廟

    五天之內站上多了 14 篇文章與 7 尊神祇條目,另有 14 篇既有條目完成再查核、7 尊補上神像圖,神祇條目從 131 增至 138、文章從 71 增至 85,合計 45 支自動產出的變更經人工終審後上線。這一批讀起來有個共同點:多數文章在做同一件事——把一個被當成單一神明的稱號拆開來看。水仙尊王其實是五位一組,而哪五位各家說法不同;「大聖爺」不一定是孫悟空;達摩臉上最好認的三樣東西,宋朝以前都還沒有;盤古在台灣只有十二間廟,卻用了八種不同的寫法。同一批的再查核也翻出一個對照:全台以「月下老人」登記主祀的廟只有一間,月老幾乎都住在別人的廟裡。

    工程細節

    九月十一日到十五日之間,站上合併了 45 支自治管線產出的變更:14 篇文章、7 尊神祇建檔、14 篇再查核、7 張神像圖,以及 3 篇開發日誌自己(PR #217–#261)。神祇條目 131 → 138,文章 71 → 85。

    上一批(9/03–9/10)的主軸是王爺補完與為了中秋這個日期反過來指定選題。這一批沒有被指定主軸,但成品出來後,十四篇文章裡有六篇在做同一件事。

    一、拆名單:一個稱號底下不一定只有一位

    • 〈水仙尊王是哪五位?〉——水仙尊王不是一位神,是「一帝二王二大夫」五位一組。問題在於到底哪五位,坊間版本浮動;文章依古蹟指定文件等來源把各版本並列,不代為裁決。
    • 〈諸府千歲:一個「不必點名」的神明類別〉——台灣的王爺以姓氏區辨、依尊數命名,三尊叫三府千歲、五尊叫五府千歲。那多到不好數、或各廟組合不同的呢?就叫「諸府千歲」。這是一個刻意不點名的類別,全台 18 間廟。
    • 〈大聖不只有齊天〉——看到「大聖爺」就想到孫悟空是很自然的聯想,但不一定對。齊天之外還有通天大聖、丹霞大聖,福建原鄉的大聖稱號原非齊天所專用。
    • 〈達摩祖師怎麼認〉——深目高鼻、風帽、大耳環、滿腮絡鬚,好認到讓人以為那本來就是他的樣子。文章把這三樣標記逐一往回查,宋朝以前的圖像裡都沒有。
    • 〈盤古:台灣十二間盤古廟〉——開天闢地的創世神,在台灣只有十二間廟主祀,分散九個縣市,用了八種不同的寫法:盤古公、盤古帝王、盤古萬歲、盤古聖帝……名稱的分歧本身就是這篇的主題。
    • 〈邢府千歲:一尊鸞堂的王爺〉——這尊王爺的廟有超過三分之一叫「堂」而不叫「宮」或「廟」,指向鸞堂系統。廟名的字面差異透露了信仰網絡的來歷。

    這六篇湊在一起不是規劃出來的,也不要把它說成機制的功勞。build-article 的成題條件只有兩個:主祀廟數夠多、站上沒有以它為題的專篇——機制不認得「名稱分歧」這回事。

    比較站得住的解釋是:名稱分歧的神祇本來就容易長期卡在這個位置。它們在登記快照裡以多種寫法分散計數,各寫法都不特別顯眼,卻加總得出可觀的廟數;而正因為「到底在講誰」不好界定,寫專篇的成本高,也就一直沒人寫。build-article 只認廟數與有無專篇,於是這類題目在佇列裡越積越前面。至於「拆名單」這個切入角度,是起草時自己挑的——同一批題目大可以寫成六篇平鋪直敘的神祇介紹。

    二、儀式與節慶:具體到一副糕模、一頂小轎

    另外六篇走的是相反方向,不拆名單而是往細節裡鑽:

    • 〈保儀尊王:迎尪公〉——台北盆地南緣的聚落每年把神像請到自己庄頭繞行田間驅蟲,一頂四個人就扛得動的小轎,和一只沒有神像的香爐。
    • 〈東嶽大帝:打城〉——臺南東嶽殿的亡魂救度儀式,紙糊的枉死城、法師劃破城門請出魂身。
    • 〈普庵祖師與澎湖小法〉——澎湖公廟裡從學齡孩童開始受訓、赤腳行法、不收分文的義務儀式人員,奉宋代臨濟宗高僧為祖師。
    • 〈法主公的紅龜節〉——一個被改過期的神明生日(臺北法主公廟廟方受訪說明原在農曆七月廿三),與「加倍奉還」的乞龜。
    • 〈太陽星君:九豬十六羊〉——農曆三月十九太陽公生,府城人用糕模捏出九隻豬十六隻羊;尖嘴是羊、寬口是豬。整篇只談這一盤祭品。
    • 〈田都元帥與西秦王爺:西皮福路之爭〉——北管兩派,福路用椰子殼做的殼仔絃拜西秦王爺、西皮用桂竹筒做的吊規仔拜田都元帥,清中葉起蘭陽地區的子弟團因此長期對立。

    還有 〈巧聖先師〉:台中東勢主祀魯班的開基祖廟,來歷不在典籍裡而在一片伐木匠寮中。這篇是本批唯一從「一間廟的身世」寫起的。

    三、再查核翻出的一個對照:登記統計得到廟,統計不到香火

    十四篇再查核裡,月老神君那篇正好撞上中秋特輯的籌備期,結論值得單獨記一筆。

    原本的條目說月老是「都會化浪潮中爆紅」的信仰,列了霞海城隍廟、龍山寺、大天后宮、嘉義城隍廟。再查核補進內政部 2026 年 6 月的寺廟登記快照後,出現一個看起來矛盾的數字:全台以「月下老人」為登記主祀神祇的寺廟,只有 1 間(新北市萬里區萬里情月老廟)。

    矛盾是表面的,落差本身才是重點——月老在台灣幾乎不自己開廟,祂住在別人的廟裡。 龍山寺後殿的月老廳、大天后宮後殿的月老公、嘉義市城隍廟三樓的月下老人殿,都是偏殿與後殿的位置,而偏殿的神明不會出現在主祀登記欄裡。

    這條提醒對整站都適用:本站大量的「全台 N 間廟主祀某某」敘述,全部來自同一份登記快照,而那份資料統計得到廟,統計不到香火。同一篇也把霞海城隍廟月老的來歷補成有出處的版本(依臺史博「臺灣女人」:1971 年一位老太太捐獻、高 43 公分神像、鉛錢取臺語「緣」的諧音、成婚後以訂婚禮餅還願),並把 2000–2002 年的還願對數照錄年份、不外推到今天。

    另外,這輪再查核也把「紅線斷了代表緣分將至」這類說法從通則降回各廟自述——各廟解釋不同,本站並列而不裁決,效驗比較一律不做。

    四、順手修的一個機制漏洞

    同期修掉 post-merge 的一個缺口(1feba82)。行事曆型(calendar-feast)的 PR 是往 festivals.yaml 追加條目,沒有 reviewStatus 可翻,需要另一套收尾判準。這套判準當初與 open-prs 那一半同批寫在同一支分支上,但只有 open-prs 那半進了 main,收尾那半漏移植——結果是 main 從來沒有過行事曆任務的自動收尾,四筆早已 merge 的任務(軒轅黃帝 #208、蕭府王爺 #162、金府千歲、輔順將軍)就一直停在 in-review,佇列上看起來像還沒做完。

    補上之後判準改成「festivals.yaml 裡已經有 deity: <slug> 這筆」= PR 已進 main = 標 done,純佇列狀態、不動檔案、不產 commit。另外加了一道守衛:原版直接對解析結果 .map,festivals.yaml 哪天頂層不是陣列就會 TypeError 崩掉整支 post-merge;現在解析失敗只跳過這一段。

    教訓和上一節的分享圖是同一種:一次改動拆成兩半送進 main,只有被別的流程天天用到的那一半會被發現漏掉。

  4. 功能

    中秋特輯備齊了,等 9/18 自己上架——順手修掉一個「一啟用就壞」的分享圖

    九月七日上線的中秋特輯,導言講的是「先看紅線怎麼求,再看為什麼台灣最旺的月老都住在城隍廟」,但篇目裡一篇月老都沒有——那三篇比特輯頁晚一天才寫完上站,而上站之後沒有任何機制會把它們補回篇目,導言就這樣指著不存在的目次講了四天話。這次把三篇月老排進篇目最前面,並把分享用的中秋專屬圖卡打開。打開的瞬間特輯頁就壞了:那張圖的程式碼從第一天就寫好,但設定一直被註解著,等於從來沒真的跑過一次。特輯本身不需要手動上架——中秋是國曆九月廿五,首頁橫幅會在提前七天、也就是九月十八那天,隨每天凌晨的重新部署自己出現。

    工程細節

    節慶特輯機制是 9/06–9/07 那兩天做的(見〈節慶特輯改成資料驅動〉),第一筆新特輯就是中秋・月老聖誕。機制做完、特輯頁也開得起來,但它其實只完成了一半。

    導言講了月老,篇目沒有月老

    src/content/specials/mid-autumn.md 的策展導語寫得很明確:「這一頁把月老聖誕當主角:先看怎麼求紅線、喜糖與還願的規矩,再看為什麼台灣最旺的月老都住在城隍廟」。

    而 Phase 1 的篇目掛的是既有的五篇——土地公秋報、福德正神、孚佑帝君的分手傳說、擲筊、備供品。月老主軸的三篇一篇都不在。

    原因是時間差:特輯檔是 9/06 建的(10345df),而那三篇——〈月老怎麼求:紅線、喜糖、還願〉、〈為什麼台灣最旺的月老都住在城隍廟〉、〈中秋為什麼變成烤肉節:被燻掉的姻緣夜〉——是 9/07 才經自治管線終審上線的(PR #207、#209、#210)。建檔當下它們還不存在,寫不進篇目;等它們上線之後,也沒有任何東西會回頭把它們放進去。

    這是資料驅動機制一個不明顯的副作用:articles 是一份手寫清單,頁面永遠組得出來,有幾篇排幾篇,少了什麼它不會抱怨。Phase 1 特地補的斷鏈守衛(af8bc6e)擋的是「連到不存在的內容」,擋不了「該連的沒連」——前者會炸掉 build,後者只會安靜地讓導言說謊四天。

    補上的三篇依導言順序排在最前:yuelao-red-thread-how-to、yuelao-in-city-god-temples、mid-autumn-bbq-and-marriage-night,後面接原本的五篇。

    註解掉的功能不算完成

    同一筆順手把 og: 那行取消註解,指向新產的 /og/special-mid-autumn.jpg——2400×1260,卡面帶「9/25(農曆八月十五)」,用 Phase 1 就寫好的 npm run gen:og:special mid-autumn 產出。之所以拖到現在才打開,是因為卡面主角要用月老神像,而那張圖 9/07 才進站。

    取消註解、開 dev、/specials/mid-autumn/ 回 500:

    ENOENT: no such file or directory, open '~/git/public/og/special-mid-autumn.jpg'

    路徑少了一層 miao/——它跑到 repo 外面去了。

    成因有兩層,都是抄程式碼抄出來的:

    1. 算 og 圖雜湊時,用 import.meta.url 往上退四層 ../../../../public 定位檔案。這串 ../ 是從 realms/underworld/ 那頁抄的,而那頁比 specials/[slug].astro 深一層——同樣的 ../ 數量,在這裡就退過頭。
    2. 更麻煩的是同一份程式碼在 dev 與 build 的起點不同:dev 從原始檔解析、build 從打包後的 chunk 解析,兩者深度不一樣。就算把 ../ 數對了,也只會有一邊對。

    改成用 process.cwd() 定位 public/——dev 與凌晨 04:30 的部署,工作目錄都是 repo 根。dev 回 200、npm run build 產物的 og:image(含 ?v= 雜湊)也驗過。

    真正的教訓不在路徑:Phase 1 就「寫好」的分享圖功能,因為那一行始終被註解著,從未執行過一次。 驗收表上它是打勾的,因為腳本產得出圖;但頁面讀那張圖的那段程式碼,是壞的。一個被註解掉的欄位,等於一段沒有測試覆蓋的程式碼,只是看起來像完成的。

    不用手動上架

    特輯什麼時候出現在首頁,是算出來的,不是排程貼上去的:window.lunar 是農曆八月十五、leadDays: 7。2026 年的八月十五是國曆 9/25,所以首頁橫幅會在 9/18 自己出現(文案「再 {days} 天月老聖誕」,當天改成「今天八月十五,月老聖誕」),過了就自己收掉,特輯頁則永久留在 /specials/ 索引裡。

    換檔靠的是每天凌晨的重新部署——這也意味著時效視窗的誤差上限是一天,且如果某天部署失敗,橫幅就會停在前一天的狀態。目前接受這個代價(靜態站的取捨),沒有改成客戶端計算。

    寫這篇的時候(9/11 起草、9/17 整理)橫幅還沒上。下一個要注意的是明年:og 卡面印著國曆日期,每年視窗開始前要重跑一次 gen:og:special,這件事目前只寫在那一行的註解裡,沒有任何機制會提醒。

  5. 資料工程 ADR 0012

    一次列表請求要讀四萬列——Cloudflare 兩封通知信之後補的額度治理

    廟宇列表頁每次打開,資料庫都要把一萬兩千多間廟從頭數一遍,只為了排出畫面上的五十筆。這件事在流量小的時候看不出來,直到 Cloudflare 通知免費額度即將開始強制執行——照當時的算法,一天大約一百二十次瀏覽就會把全日額度用完,用完之後整個廟宇區塊會直接壞掉到隔天早上八點。修法是把「哪尊神的廟最多」這種不隨每次請求改變的數字,改成資料匯入時算一次存起來,查詢只讀不算,單次成本降到約五十列。收拾完讀取,寫入那頭又寄來第二封信:每天凌晨的資料同步都在重灌整個資料庫,而且原本以為早就改好的增量同步,因為平台自己塞在資料庫裡的一張內部表,一次都沒有真正跑到過。

    工程細節

    八月廿四日收到 Cloudflare 通知:九月一日起開始實際執行 D1 免費方案的每日上限(讀取 500 萬列/寫入 10 萬列),並直接點名本專案的帳號「regularly exceeds these daily free tier limits」。超量的後果不是計費而是中斷——當日剩餘時間所有查詢回錯誤,到台灣時間隔日早上八點才重置。對本站就是 /temples/all/ 與全部 12,414 間廟的個頁在某個時點之後全數 5xx。

    翻程式碼後歸因很明確,而且與流量無關——是單次請求的讀取列數異常。

    元凶:把不隨請求變的東西放在請求裡算

    列表頁的預設排序是「熱門度」,也就是同一尊主祀神底下有幾間廟。當時的寫法是每次請求跑一段 WITH pop AS (… GROUP BY …),全表掃過 12,414 列算出 1,772 組計數,再跟主表 join、全表排序,最後取 50 列。加上另一條 SELECT COUNT(*) 又是一次全表掃。

    EXPLAIN QUERY PLAN 以正式資料實測:單次頁面請求約讀 37,000–50,000 列。取四萬估算,5,000,000 ÷ 40,000 ≈ 每日 125 次瀏覽即用罄。

    放大器是變體網址:該路由 249 頁 × 4 種排序約 1,000 個可爬網址,外加任意 ?q=。爬蟲繞完一輪就是 4,000 萬列,全日額度的 8 倍,幾分鐘打爆,而且每次重爬都重演一次。?page=99999 這種輸入更會讓資料庫掃完整條索引才回空陣列——等於把額度開放給任何按得動網址列的人。

    還有一個被誤信的防線:兩個頁面都設了 Cache-Control: public, max-age=…,先前以為「有快取擋著」。實際上 Pages Functions 產生的回應預設不進 CDN 快取,那個標頭只作用於訪客自己的瀏覽器。每個新訪客、每隻爬蟲都是實打實打進資料庫。

    一併盤點了不是問題的部分,避免亂改:廟宇個頁的三條查詢都是主鍵/索引點查,單頁個位數到數十列,Googlebot 全爬 12,414 頁也遠低於上限。

    修法:熱門度是輸入資料的函數,不是請求的函數

    同一份資料快照下,每次請求都算出一模一樣的 1,772 組計數。搬到 ETL 一次算好、存成 temples.pop 欄位,再對四種排序各建對應索引,查詢就退化成「照索引直取 50 列」。這符合站上早就定過的原則(ADR 0002):機器量級資料走 ETL 管線,runtime 只讀不算。

    配套三件:無搜尋時的總數改讀 ETL 預先寫好的 meta.row_count(一列點查取代 12,414 列全掃,讀不到則退回原寫法,讓部署順序顛倒時只是變慢而不會壞);超界頁碼直接短路、連查都不查;robots.txt 封鎖 /temples/all/? 全部變體,頁面對變體輸出 noindex。列表本體仍開放收錄,12,414 間廟的個頁另有 sitemap 完整供給,封鎖不影響任何頁面被找到。

    實測降幅比預估的還漂亮。近七天逐查詢統計:舊的那條 WITH pop 跑了 156 次、讀 1,190 萬列,每次 76,282 列(比原先估的四萬還高一倍);新查詢單次 51 列。約 1,740 倍。可承受的列表瀏覽量從每日約 125 次提升到約 98,000 次——額度不再是流量天花板。

    有一條紀律得寫下來:ORDER BY 與索引是一組耦合契約。改查詢的排序而沒同步改 ETL 的索引定義,SQLite 會靜默退回全表排序,讀取列數一夜回到萬級,而且不會有任何錯誤訊息。兩處都加了註解警語。

    讀取解決後,寫入變成新的瓶頸

    部署後兩天覆核儀表板,讀取側完全符合預期,但同一份儀表板暴露了先前沒盤點到的問題:每日寫入 171,000 列,上限是 100,000。

    而且這在本次改動之前就已經超標(134k)——新增的三條排序索引讓它更糟,但不是成因。成因是同步腳本的冪等全量重灌設計:每次 DROP 重建、灌回全部 40,408 筆資料列,而寫入額度把每條索引項各算一次。算術上,光是三張表的資料列加各自主鍵就約 8 萬列,任何 schema 下的一次全量重灌都塞不進 10 萬列。減索引救不了。

    後果比讀取超標更難察覺:同步會在中途撞上限而中斷,而腳本是先 DROP 再 INSERT——留下一個資料只灌一半的線上庫,網站查得到廟卻查不全,且不會報錯。

    方案有二。升級付費方案($5/月)含 5,000 萬列寫入,零程式改動;或改增量同步,只寫差集。選增量——除了維持免費之外,決定性的理由是它修掉了「先 DROP 再 INSERT」這個結構性風險,而這個風險與額度無關,付費方案一樣存在,只是被額度充足掩蓋。增量帶來兩個全量拿不到的性質:不 DROP,所以任何時刻每一列都是舊值或新值,不會出現「不見了」的列;寫到一半中斷,重跑會自己補完並收斂。

    這也讓 ADR 0002「不做增量」的原則得到一次有依據的例外。當初排除增量的理由是「秒級量體不需要那個複雜度」,成立——但那是在寫入無上限的前提下。前提變了,結論跟著變。

    實作把差集抽成純函式模組(d1-diff.mjs,零 I/O),因為同步腳本本身會動到正式資料庫、沒辦法在開發機以外實跑。以 12,414 筆實測:全量重灌 86,744 列 → 差集 848 列,約 100 倍;中斷後續跑(只套用 123 條中的前 61 條)會再次比對出 62 筆待補、收斂到正確狀態。

    第二封信:增量同步從來沒有跑到過

    九月五日又收到通知,這次是寫入超標。翻部署 log,增量上線後的每一天都是同一行:

    [sync-d1] 全量重灌(線上 schema 與本機不同):40,684 筆資料列,估算寫入約 171,702 列

    指紋日日相同(資料根本沒變),卻日日全量重灌 171,729 列。指紋護欄與增量路徑一次都沒走到。

    成因很小:每個 D1 資料庫都內建一張 Cloudflare 內部用的 _cf_KV 表,會出現在 sqlite_master;本機產物沒有。比對線上 schema 時只排除了 sqlite_%,線上因此永遠多一條,字串比對永遠「不同」,程式就照設計「schema 不同則自動全量」。排除掉再比,兩邊 11 條 schema 完全一致。

    先前的往返測試只覆蓋差集純函式,schema 比對那段寫死在主流程裡、沒有測試,所以漏網一週。

    附帶影響:D1 對單次匯入是整批放行、事後才封鎖後續寫入,所以每天約 04:30 到隔日 08:00 之間,/api/track 與 /api/report 的寫入都在回錯誤——讀取不受影響,網站本身沒有停機,所以沒人發現。

    兩個教訓值得留著:

    • 「線上」不是「本機的鏡像」。 任何拿線上 sqlite_master 當比對基準的地方,都要先問一句「平台自己會不會塞東西進來」。所以過濾用的是字首(_cf_/sqlite_)而非白名單單一表名。
    • 自動退回全量是一道太安靜的失敗。 schema 不同時腳本只印一行 log 就照跑 17 萬列。每日排程無人盯 log,連續一週沒人發現,直到 Cloudflare 來信。已在 log 內註明成因;日後再看到連續多日「線上 schema 與本機不同」,先懷疑比對本身。

    一個部署順序的陷阱

    改 ETL schema 的那次部署必須先資料、後程式——先部署程式會讓新查詢對著沒有 pop 欄位的舊資料庫,列表頁直接 500。而現成的部署腳本內建順序正好相反(build → 部署 → 同步 D1)。平時無妨,但凡是動到 schema 的那一次,這兩步之間會有數十秒到數分鐘的 500 空窗。

    紀律:日後任何改動 ETL schema 的部署,一律先手動跑 ETL + 同步,再交給部署鏈。

    還沒解的留在 backlog:搜尋的 LIKE '%…%' 仍是全表掃(約 25,000 列/次,暫以 robots 封鎖爬取避免被機器放大,FTS5 化另案);深頁的 OFFSET 成本;以及來源資料真的大改版那天,差集可能退化成幾乎全刪全插——腳本會先印估算值並警告,因為不 DROP,超限中斷不會缺列,隔天重跑續完即可。

  6. 功能 ADR 0002

    節慶特輯改成資料驅動——鬼月那套版型,往後每個節慶直接套

    八月的鬼月特輯證明「限時入口+常駐內容」這個作法有效,但它整組是寫死在首頁裡的一次性程式碼——換成中秋就得重刻一次。這兩天把它抽成機制:一個節慶=一個檔案,特輯頁、專題索引頁、首頁橫幅與分享圖全部自動產生,什麼時候上架下架由農曆日期算,隨每天凌晨的重新部署自動換檔。導覽列的「地府專題」升格為「專題」,指向新的索引頁,過期的特輯不會就此消失在站上。第一筆新特輯是九月廿五的中秋・月老聖誕。過程中修掉一個會讓農曆十二月的特輯整組差一年的換算錯誤,也發現原本以為框架會幫忙擋的「連到不存在的內容」其實完全不會擋,改成自己在建置階段就報錯。

    工程細節

    八月十二日上線的鬼月特輯是有效的:一個有時效的首頁入口,把人帶進一頁常駐內容。但它的實作是寫死的——首頁 index.astro 裡一段 ghostMonth 區塊、一組 .ghost-banner 樣式、一支只認農曆七月的 ghostMonthWindow。中秋、重陽、下元、過年、媽祖進香季,每一個都要把這三樣再刻一次。

    所以先寫規格再動手(docs/specs/seasonal-specials.md),把它抽成資料驅動的機制。一個特輯=一個 Markdown 檔,機器欄位放 frontmatter、策展導語放正文;頁面、索引、橫幅、分享圖都由機制產生。

    幾個當下定案的取捨

    決策定案否決的選項
    特輯頁壽命頁面常駐,只有入口有時效——網址固定,每年換篇目、越滾越厚整頁時效(搜尋引擎年年收錄又掉出)
    資料放哪content collection 的 md單一 specials.yaml(散文塞不下、多特輯同檔易撞 PR)
    鬼月怎麼辦遷入成為第一筆特輯,地府專題頁本身零改動維持現狀(首頁就得留兩套時效邏輯)
    過期後加 /specials/ 索引頁,導覽列「地府專題」升格為「專題」不做索引(過期特輯只剩搜尋引擎找得到)

    鬼月那筆的處理值得多說一句:它 kind: realm,只供首頁橫幅資料,落地頁覆寫回 /realms/underworld/——界域 hub 不是節慶頁,硬套同一個版型會變形。所以機制允許一筆特輯「有入口、沒有自己的頁」。

    重構的驗收方式:對拍,不是「看起來一樣」

    ghostMonthWindow 改寫成通用 specialWindow 的薄封裝,這種重構最怕的是「大部分日子都對,某年某月不對」。所以驗收不靠目視:

    • 以改動前的舊實作為基準,2000–2050 年逐日 × leadDays ∈ {0,3,7,45} 共 74,512 組對拍,差異 0,涵蓋 2006 與 2044 兩個閏七月年。
    • 另加突變檢定,確認這個比對抓得到差異、不是空跑。
    • 地府專題頁的 build 產物與改動前逐字 diff 為零。

    三個 bug,一個比一個安靜

    一、農曆年不是國曆年,臘月類特輯整組錯一年。 specialWindow 拿 today.getFullYear()(國曆年)餵進農曆換算的年份參數。農曆日落在國曆一、二月時,它的農曆年其實是前一年——尾牙(臘月十六)算出來是 2028-01-12,正確答案是 2027-01-23;而且提前顯示的迴圈只試 [y, y+1],永遠試不到正確的 y-1,第一圈算出相差約 360 天就 break,lead-in 完全不觸發。改以今日農曆年為基準。

    這個 bug 在原本 17 個測試案例中無一涵蓋,因為鬼月與中秋都不跨農曆年——測試日期全落在農曆年與國曆年一致的月份。補了尾牙三例鎖住 2027-01-23。

    二、跨農曆年的區間會靜默失效。 農曆日以 月×100+日 編碼比對,臘月廿四(1224)到正月初五(105)的區間,k >= 1224 && k <= 105 恆為 false。md 寫好、build 過、頁面生得出來,橫幅就是不出現,沒有任何錯誤。演算法本身不動,改在 schema 加一條護欄:寫下跨年區間就 build fail 並指明要先擴充時效邏輯。把「靜默不觸發」換成「寫的當下就爆」。

    三、以為框架會擋的斷鏈,它不擋。 規格原本假設 Astro 5 的 reference() 在頁面消費時會讓 build 失敗。實測把 hero 改成不存在的 slug——build 仍 exit 0,只是靜默出貨一頁沒有主角區的特輯。追下去才知道 Content Layer 下 getEntry() 找不到 entry 是 console.warn 後回 undefined,不 throw;而程式裡的 hero?. 與 .filter(x => x !== undefined) 正好把它吃掉。

    過程中還誤判過一次:把某個欄位改成不存在的值時 build 確實 exit 1,一度被當成「框架會擋」的證據;查下去發現那個 exit 1 來自專案自己另一支程式的 throw,框架只印了一行 warning。兩次實測才定調。

    修法是自己在 build 期 fail fast:hero 找不到就 throw、三個陣列改用 resolveAll() 一次列出所有斷鏈 id。要分清楚的是——斷鏈是打錯字,要擋;文章尚未終審是流程狀態,要放行(印警告、不擋 build),兩者語意不同。

    最終整合審查抓到的兩件事

    上線前跑了一次整合審查,抓到兩個不是 bug 但都是設計漏洞的東西:

    • 同一次 build 內兩個「今天」。 首頁橫幅印「今日廿五」,地府專題頁印「今日廿六」——ghostMonthWindow 的預設參數還是 new Date(),而 specialWindow 早已改用統一的 siteToday()。跨午夜建置或帶日期預覽時就分岔。
    • 一份沒人讀也沒人維護的假 SSOT。 ghost-month.md 的 articles 五個 slug 與地府專題頁裡的清單逐字重複,但真正被渲染的是後者——前者從未被讀取過。kind: realm 的特輯根本不經過特輯頁的守衛,這三個欄位形同無人檢查的死角。修法是 schema 直接禁止 kind: realm 填這三個欄位,從源頭堵掉。

    目前的已知邊界

    照實記:specials 的查核欄位目前沒有任何消費端——一筆 reviewStatus: draft 的特輯 md 一 commit 就會上首頁橫幅,也不會出現在「過期未查」掃描裡。特輯由人手寫、量少,暫時可接受;日後若改由管線產製,得先補上。此外索引頁沒有分頁或年度分組,特輯累積到數十筆會變成一面無序卡海;landing 也沒驗證路由存在,填錯會靜默出貨 404 連結。

    分享圖是手動逐筆觸發(npm run gen:og:special <slug>),刻意不進全站批次——時效卡的卡面帶當年國曆日期,每年該特輯視窗前要重跑一次。中秋這筆的主角月老神像圖當時還沒 merge,所以 og 欄位先註解掉、退回站台預設卡。

    Phase 1 的目標日是 09-12:鬼月橫幅 09-11 自動下架、中秋 09-18 進入提前顯示,中間留一週空窗驗證「什麼都不顯示」也是對的。

  7. 內容

    王爺這一支補齊了,順便第一次為了一個日期產內容

    這一週站上多了 14 篇文章與 8 尊神祇條目,另有 12 篇既有內容完成再查核、9 尊補上神像圖,神祇條目從 123 增至 131、文章從 57 增至 71,合計 46 支自動產出的變更經人工終審後上線。內容集中在兩處:一是王爺信仰——五府千歲底下的范、何、黃、劉、徐、伍六姓各自獨立成篇,加上五年千歲的十二瘟王輪值、東港燒王船、五福大帝與家將陣頭的起點,把台灣廟數最多卻最難梳理的這一支補成完整的一組;二是中秋,三篇月老與中秋的文章是刻意排進去的——第一次為了「九月廿五要有內容可看」這個日期,反過來去指定自動流程該寫什麼。

    工程細節

    九月三日到十日之間,站上合併了 46 支自治管線產出的變更:14 篇文章、8 尊神祇條目、12 篇再查核、9 尊神像圖、1 筆行事曆,以及 2 篇開發日誌自己。神祇條目 123 → 131,文章 57 → 71。

    數字之外,這一批的內容分布不是隨機的。

    一、王爺:把「一個姓一尊」拆開寫

    王爺是台灣廟數最多的一支,也是最難梳理的一支——同一個「五府千歲」,在不同廟指的是不同五位。這一週補的六尊都是同姓氏層級的獨立條目:范府、何府、黃府、劉府、徐府、伍府千歲。從別名就看得出各家的差異有多大:范府千歲掛「五王范府千歲/代天巡狩」,徐府千歲在有些廟是「徐府三千歲」、有些是「七千歲」,劉府千歲則另有「劉部大帝/濟世真君」這種完全不像王爺的稱號。

    配套的文章走的是制度與源流而非單尊介紹:

    • 〈五年千歲:不是五位,也不是五年〉——十二瘟王的輪值制度與馬鳴山的吃飯擔。這個題目光是名字就有兩層誤解要拆。
    • 〈燒王船(東港迎王):一艘造了三年的船,和那句「不要回頭」〉
    • 〈五福大帝:台灣家將陣頭的起點,是一座福州軍人的瘟神廟〉
    • 〈五顯大帝不是五福大帝——一組被權威著作寫混了半世紀的神明身分〉
    • 〈廣惠尊王:東晉宰相謝安,如何變成台灣的王公爺〉

    到這一批結束,站內王爺題已有 9 篇。這是一支寫完才看得出形狀的信仰——單看任何一尊都像地方小神,九篇擺在一起才是一套代天巡狩的輪值與送瘟制度。

    還在待終審佇列裡的下一篇是〈諸府千歲——不必點名的那一類王爺〉,切的角度是「資料現象」:全台 18 間主祀廟在登記表上寫的就是「諸府千歲」四個字,零變體;而台南有一間廟名把三個姓寫在招牌上、主祀欄卻登記「諸府千歲」。用自己的廟宇資料庫回頭檢視自己的資料庫濾鏡,這是資料量到了才寫得出來的題目。

    二、中秋:第一次反過來指定管線寫什麼

    自治管線一直以來的取題邏輯是掃描器發現缺口、依分數排序——覆蓋率哪裡薄就補哪裡。這套邏輯對「累積覆蓋率」很有效,但它沒有日期概念。八月十九日的紀錄裡就吃過一次虧:整個農曆七月,題庫裡只有一筆鬼月題,而且早被誤擋、永遠抽不到。

    這次做法反過來。九月六日寫節慶特輯規格時,一併把中秋特輯需要的三篇文章直接種進工作佇列,分數給 9001–9003——遠高於掃描器產生的任何分數,確保隔天的每日取題一定先拿到它們:

    1. 〈月老怎麼求:紅線、喜糖、還願——第一次去拜的完整流程〉:主角操作文。
    2. 〈為什麼台灣最旺的月老都住在城隍廟——一座陰間衙門的婚姻科〉:從「城隍管生死簿、月老管姻緣簿」的簿冊命運觀切入。
    3. 〈中秋為什麼變成烤肉節:被燻掉的姻緣夜〉:中秋原本是姻緣夜(拜月求姻緣、「聽香」、月老聖誕),如何被 1980 年代的烤肉風氣蓋掉;結尾的翻轉是七夕拿走姻緣、中秋拿到烤肉,兩個節日交換了工作。

    三篇都在九月七日當天取題、產草、稽核、開 PR,隨即終審上線,佇列狀態全部 done。加上先前已有的〈中秋土地公生〉,中秋特輯 09-18 進入提前顯示時,篇目是齊的。

    這件事的意義不在三篇文章,而在管線多了一種用法:掃描器負責「補洞」,人負責「插隊」。分數欄位原本只是排序用的量綱,現在也是一個插隊的入口——代價是這個入口沒有任何節流,塞太多會直接排擠掉掃描器的產出,目前靠人自己克制。

    三、順帶一提的兩個小數字

    12 篇再查核裡有鍾馗、八仙、地基主、灶神、趙公明、註生娘娘等——都是站上寫得早、來源連結最可能失效的一批。9 尊補上的神像圖裡包含月老神君,正好是中秋特輯的主角,圖 merge 之後特輯頁的主角區就會自動帶圖、分享圖也才產得出來。

    內容與功能這兩條線第一次咬在一起:規格排了一個日期,管線去產文章,圖像流程去產圖,特輯機制在那天自動把它們拼成一頁。

  8. 視覺

    首頁封面換人——近期聖誕的神像取代那縷香煙

    首頁最上方那縷從創站第二天就在的香煙,現在會在近七日內有神明聖誕時,換成該尊神的神像。點下去可以直接到那尊神的條目,右下角「近期聖誕」浮窗裡對應的那一列也會亮起金色徽章,讓封面上是誰一目了然。沒有合適神像的日子,香煙照常回來。

    工程細節

    首頁 Hero 的香煙是 2026-06-12 換上的——當時砍掉創站日那張標籤打架的星圖,改成一縷隨捲動流轉的煙,圖檔零依賴、純 SVG。撐了兩個半月,問題不在它不好看,而在它永遠一樣:站上每天都有神明聖誕,首頁卻完全看不出來。

    挑選規則刻意寫寬

    新增的 heroCoverDeity() 取「近 7 日(含今日)內有聖誕、且 image 欄有值」的神明,依日期由近而遠取第一尊;全部沒圖就回 null,走原本的香煙。

    直覺上應該是「取最接近今天的那一尊」,但實測否掉了這個寫法:112 尊神祇裡只有 38 尊有神像圖,嚴格取最近者的話多數日子會落空。放寬成近 7 日窗口後,現有圖量下一年有 199 天出得了封面;只看最近一尊會再砍掉一大半。這條規則不必隨圖量調整——神像圖補齊後命中率自然上升。

    同一支 commit 順手補了 siteToday():首頁三個時效區塊(封面、鬼月橫幅、聖誕浮窗)過去各自 new Date(),跨日那一瞬間有機會算出不同日期。現在共用同一個「今天」,並開了 SITE_TODAY 環境變數供預覽時效版面用。

    踩到的雷:神像整塊上移 333 像素

    神像的光效元件 DeityGlow 原本只服務神祇個頁,這次加了 variant('page' | 'hero'),特效邏輯兩者完全共用。hero 版以為只要覆寫 inset: 0 就好——結果神像整塊往上跑了 333 像素(實測 y = -268)。原因是 page 版在桌機帶了 top: 50% 加 translateY(-50%) 的置中寫法,只改 inset 而不一併把 transform 設回 none,那個 -50% 就原封不動地作用在新的定位基準上。

    同時把初始化從單一元素改成 querySelectorAll().forEach(),多實例才安全——首頁與個頁現在可能同時存在兩組。

    標名放哪裡,桌機和手機答案不同

    封面有神像時,香煙 SVG 與餘火光點是整組不渲染,不是用 CSS 隱藏。神像包在只框住自身的 <a> 裡——連結若覆蓋整個 Hero,讀者點空白處也會被導走。

    標名的位置試了兩種:桌機放神像上緣,因為右下角長期被「近期聖誕」浮窗(fixed; right: 24; bottom: 20)佔著。手機(≤768px)則貼 Hero 右上而非文字下方——手機底部同樣被浮窗佔住,放下面等於看不到。窄版下神像與標題必然交疊,索性把底圖刷淡到 0.5、遮罩左緣從 58% 起,讓神像退成右上角的一片暗金紋理。

    浮窗那邊要補兩件事

    徽章掛在「近期聖誕」浮窗的命中列上,是清單裡唯一的實心色塊。但兩邊窗口不一樣寬:封面取近 7 日、浮窗取近 5 日,封面那尊落在浮窗窗外時徽章根本不會出現,得補一列到清單末尾。

    另一個是命中列顯示的是節慶名時(例如中元節對應三官大帝),徽章要補上神名——否則讀者對不起來 Hero 上那尊是誰。同一尊神可能同時來自 deities 的 feastDay 與 festivals.yaml 兩處,徽章只標第一列。

    規格文件的自我否定

    docs/specs/homepage-hero.md 第 7 節原本白紙黑字寫著「不要引入燈籠、廟、神像等圖檔」。這次等於推翻其中一項,處理方式是在該條與文件標頭都標明神像一項被新增的第 10 節〈封面人物〉覆寫,其餘(燈籠、廟、節點圖)仍然不要——而不是把舊條文刪掉當作沒發生過。

    驗收在雲端沙箱跑完整 build 加 Playwright;本機掛載區跑不了 build。

  9. 營運 ADR 0007

    產能提上去之後,卡住的變成人這一端——自治管線的三處營運調校

    八月中把每天的自動選題從 4 題提高到 8 題以後,瓶頸換了位置:草稿產得出來,卻送不出去、也看不清楚。這一週補了三處——通知依性質分到三個獨立頻道、待開審佇列改成先進先出並把每次送出的上限對齊到 8、以及把「需要人處理」這個狀態鎖成不可被自動流程覆寫。同一段時間站上多了 15 篇文章與 10 尊神祇條目,另有 16 篇既有內容完成再查核,神祇條目從 112 增至 122。

    工程細節

    八月十二日那次調校把每日自動選題從 4 筆提到 8 筆,八月十九日的紀錄確認了產能確實跟上。接下來這一週暴露的是另一組問題:產出變多之後,人這一端的介面全部不夠用了。三處毛病成因各異,但共通點是它們都是在低產量時看不出來的設計。

    一、通知被自己淹沒

    站上的營運通知從 2026-07-25 起收斂成單一頻道,當時訊息量小,混在一起沒差。產能翻倍後,「有 PR 等你審」和「有神像圖等你產」這兩類需要人動手的訊息,被例行的部署摘要、備份回報、掃描結果擠到看不見。

    做法是在 notify.mjs 補 prsThreadId、imageThreadId 兩個覆蓋鍵,沿用既有「未設即落回預設頻道」的語意——沒設定時行為完全不變,仍是安全的 no-op。

    有兩個細節值得記:

    • 開 PR 的通知散在兩支腳本裡。open-prs.mjs 與 post-merge.mjs 的通知標題同樣渲染成「自治開 PR」,只搬其中一支會讓同類訊息分裂到兩個頻道。六個通知點(停機開關、基底同步失敗、收尾摘要)全部要帶上頻道 ID。
    • 同一支腳本的兩個通知點不該同進同出。image-bridge.mjs 發的「待產圖」提示詞包是待辦,收尾摘要是營運訊息;只有前者吃新的頻道鍵,後者刻意落回預設。這條分家規則寫進了檔頭註解。

    規則散在三支腳本裡就會重蹈 7 月那次改動的覆轍,所以 ops-notifications.md 的路由段改寫成一張「標題行 → 來源 → 頻道鍵 → 現值」的總表。

    二、待開審佇列餓死了隊尾

    待開 PR 的記錄積到 20 筆,最舊那筆等了七天還沒送出去。查下來是兩個獨立成因疊在一起:

    1. 排序是檔名字母序。每天新產的檔案多以 b、d 開頭,永遠插到隊伍前面;速率上限一卡,w、x、y 開頭的尾端檔就被無限插隊,餓死在隊尾。
    2. 速率上限沒跟著調。每次送出上限 5 筆是 2026-07-16 定的值,當時每日選題上限還是 3 筆。八月十二日把每日選題調到 8,這裡漏調——每天最多產 8 筆草稿、只送得出 5 筆,每天淨積 3 筆,積壓是結構性的而不是偶發。

    排序鍵改成記錄自帶的 createdAt,早期缺這個欄位的退回檔案 mtime(語意一樣是「先產先送」),同時間者再用檔名排序保持穩定。上限改成 8,並開了環境變數供一次清積壓用。單次 8 筆仍在 ADR 0007 的「單次不超過月額十分之一」之內(月上限 250,十分之一是 25)。週更的日誌題與廟宇祈求題落在同一天時會短暫超出,由低產日吸收。

    驗證用 --dry 實跑:隊頭從字母序的 baoyi-zunwang-recheck 換成 createdAt 最舊的 zhunti-pusa——那筆是 8 月 14 日產的。

    三、「需要人處理」這個狀態會被蓋掉

    這件是三件裡最危險的,而且差一點就完全沒人會發現。

    八月二十七日一次日誌 run 一口氣產了 4 篇草稿,共用同一個任務編號。送 PR 時逐筆處理,其中一篇撞上了敏感題材的字面攔截網,依規定停手:不開 PR、刪草稿、任務標成「升人工」。接著後面三篇各自成功,各自把任務狀態改成「審查中」——把「升人工」蓋掉了。

    於是佇列上完全看不出有東西被攔下來,草稿也已經刪了。再往下推一步更糟:等那三篇被 merge、日誌掃描器的基準日往前推進,被攔那篇的訊號日期就低於基準日,而修訂窗的設計只補「終態任務未涵蓋的訊號」,也救不回來。那一篇會永久蒸發,而且蒸發得無聲無息。

    實際是靠人工比對待開 PR 的筆數(4 筆)與已推出去的分支數(3 支)才發現的。

    修法是把「升人工」改成硬終態:任何自動流程都不得覆寫,只有帶 { force: true } 的人工介入能改,被擋下時在 console 印出解法。連收尾腳本標「完成」也一併擋——那支收尾的判定是「相關分支全數併入主線」,而被攔那篇根本沒有分支,會誤判成全部完成、換個路徑再蓋一次。

    背後的觀念修正是這句:任務狀態代表「這一窗還有沒有事要人處理」,不是最後一篇的處理結果。 掃描端早就護住了「不重開終態任務」這一側,這次補上的是「狀態機不得降級」的另一側。

    那道攔截網不放寬

    要說清楚的是:那篇被攔是因為它在複述紅線討論時照抄了規範裡的反例句子,而反例本身就是為了長得像違規內容而寫的。攔截網是刻意設計成寧可誤攔的硬紅線(誤攔成本=人看一眼),不因為誤攔而放寬詞表——要調整的是起草端。這條紀律連同事故經過寫進了 devlog.md 新增的〈T3 字面詞表與站務敘事〉一節:站務敘事在談效驗比較這類編輯決策時,用「效驗比較」「廟際比較」這類說法就語意完整了,不必觸網。

    被攔那篇後來人工重寫補回,機械稽核全過。

    這一週的產出

    三處修完之後的一週(8/26–9/2),主線併入了 60 支自動產出的變更:15 篇文章、10 尊神祇建檔、16 篇再查核、13 張神像圖、2 筆聖誕曆、4 篇開發日誌。神祇條目從 112 增至 122,文章從 40 增至 55,有神像圖的神祇來到 51 尊。

    對照八月十九日那篇紀錄的「兩週 43 支」,速率大約翻倍——而多出來的量,正好就是原本每天淨積在待開審佇列裡送不出去的那 3 筆。

  10. 內容 ADR 0007

    鬼月那六個題目,兩週內全部寫完了——八槽制上線後的第一次滿載

    農曆七月開始前補進題庫的六個鬼月題目——豎燈篙、放水燈、普渡場、跳鍾馗、搶孤、鬼門關——在鬼月結束前全部寫完上線了。同一段時間站上還多了濟公、地藏王菩薩、開台聖王、開漳聖王等文章,神祇條目從 108 增至 113,另有 16 篇既有內容完成再查核。這是自動流程從每天 4 題提高到 8 題之後的第一段完整運轉:兩週共 43 支自動產出的變更經人工終審後上線。

    工程細節

    八月十二日那批調校做了兩件事:每日取題從 4 筆提到 8 筆,以及往題庫補進六個鬼月題目。當時的紀錄裡留了一句沒把握的話——整個農曆七月,題庫裡只有〈中元普渡〉一筆題,而且它早被既有文章的通用詞誤擋,永遠抽不到;鬼月期間自治管線能取的題只剩修死連結、神祇建檔與產圖,跟節期完全無關。

    補題之後兩週,那六筆全部結案:

    題目成文
    豎燈篙招魂的高度與期限:那根竹竿本來不是為中元而立的
    放水燈寄到海上的邀請函——燈篙招陸、水燈招水,以及基隆與宜蘭的兩種做法
    大士爺與普渡場普度場為什麼長這樣
    跳鍾馗普度收尾的押煞儀式,為什麼要請你迴避
    搶孤孤棚怎麼爬、頭城與恆春差在哪,以及它為什麼被禁了三次
    鬼門關與地藏聖誕鬼月怎麼收——2026 年為什麼是九月十日,以及基隆為什麼晚一天

    值得記的不是數量,是它們沒有寫成禁忌清單。跳鍾馗那篇的收尾引資深傀儡藝師林金鍊的說法:除煞的重點其實在「引孤」——讓亡靈有所依歸,鍾馗只是介紹人,這門儀式演變至今「已經不是對立驅逐的激烈衝突,而是同步牽引的護送手段」。搶孤那篇處理的是日治禁令與戰後三次復辦。鬼門關那篇算的是曆法——為什麼今年落在九月十日,以及基隆為什麼晚一天。考據取向撐得住自動產製,這是這一批真正驗證的事。

    兩週的實際產量

    八月十三日到二十六日,併入 main 的自動 PR 共 43 支:文章 12、神祇建檔 5、既有內容再查核 16、神像圖 7、節慶題 1、開發日誌 2。文章從 33 篇增至 45 篇,神祇條目從 108 增至 113。

    再查核那 16 篇是新機制第一次真的把數字往下推——在此之前「待查」是個只會上升的數字,因為沒有任何掃描器看得見已發布的舊內容。

    八槽制也確認實際生效了。取題記錄顯示八月十三日起連續多日穩定取滿 8 筆(8/19 至 8/23、8/26 皆為 8 筆),不再是舊制的 4 筆——上一篇留的那個尾巴(規則寫進版控副本但排程 UI 上的 prompt 要人工同步一次,否則實跑仍是 4 筆)已經補上。八月至此累計取題 106 筆,月上限 250,仍有餘裕;瓶頸長期是佇列有沒有題,不是額度這句話再次被證實。

    誠實記一筆:中間有四天完全沒跑

    取題記錄裡 8/15 到 8/18 是空的,8/25 也只有 1 筆。這不是佇列沒題——同期佇列深度足夠——而是排程沒有觸發。自治管線跑在使用者的 Mac 上,機器沒開就沒有這一天。

    這件事的意思是:「每日 8 筆」是產能上限而不是保證值。兩週 43 支的實際平均約 3 支/日,與槽位數的落差主要就在這裡,其次是終審批次合併的節奏(PR 是逐日開的,人工合併集中在 8/15、8/18、8/20、8/22、8/26 幾天)。目前沒有補跑機制,漏掉的那幾天就是漏掉了;要不要補,得先決定漏跑是否值得追——照現在的佇列深度看,題目不會消失,只是晚幾天輪到。

  11. 營運 ADR 0007

    同一天兩個事故補的兩道護欄——「抓不到」不等於「沒有」,以及重複寫出來的那篇玉皇上帝

    自治流程出了兩個不同性質的問題,同一天各補一道防線。一個是查資料時網站間歇連不上,流程誤以為「這個網站對這類條目都沒資料」,於是把三篇草稿縮小範圍照樣送審——這種降級只會留在附註裡,查核表和統計都看不見,等於把品質債藏起來。往後改成先分類失敗原因,暫時性的就延後一天重跑。另一個是同一個題目隔幾天被重新認領,寫出了第二篇玉皇上帝,兩支都開了 PR、都合併得掉。新的檢查改從「還沒合併的分支」直接重建現況,不再依賴那份剛好出錯的狀態紀錄。

    工程細節

    兩個問題同一天浮出來,共通點是防線建在會出錯的東西上。

    一、把「抓不到」讀成「沒有這筆資料」

    當天的排程 run 連三題——中壇元帥、天上聖母、玄天上帝——在全國宗教資訊網都抓不到正文。run 據此判定該站「宗教神祇」類條目系統性失效,三篇草稿全數收緊採用範圍後交終審,並建議另案建 Playwright 取頁。

    同日 12 次複驗把這個歸因整個推翻:同屬神祇類的神農大帝正常抓到全文,牲醴、神龕、一炁宗主也都正常;反而非神祇的「宗教儀式」列表頁失敗。失敗與類別無關,真因是抓取端的 robots.txt 間歇 ConnectTimeout,成功率約 4–5 次/10 次,外加約 15 分鐘的負面快取。

    差一點為一個不存在的問題建一整套取頁基礎建設。

    規格因此補了兩條紀律。第一條是失敗要先分類再決定去留:暫時性失敗(robots 抓取失敗/連線逾時/5xx/回應成功但正文為空)隔 15 分鐘以上再試,最多 3 次——連續重試落在負面快取窗內只會拿回同一個錯,那是同一次失敗的回聲,不算重試;永久性失敗(404/條目下架/該站確實沒這條)才換來源,關鍵事實仍無源才升人工。

    第二條是禁止「縮範圍照發」。抓不到就把來源降級成搜尋摘要轉引、然後照樣寫成草稿交終審,問題在於降級的事實只會留在 SOURCES.md 的一句揭露裡——查核表看不見、覆蓋率看不見,等於把品質債藏進終審佇列。寧可明天重跑,也不要今天交一篇半殘的。

    同時禁止由單日單型別全敗推論站點系統性失效:一次 run 的題常是同型別,全敗只代表取樣偏誤;要斷定站點失效,須跨類別各抽 ≥3 筆且全敗才成立,且成立才進 backlog,不在 run 內臨時改管道。

    佇列為此加了 deferred 狀態。只寫規格不改碼會直接踩到上一批那個坑——upsert 的狀態保護清單沒有 deferred 的話,下次掃描就把它打回 open,隔天照樣重複取題,規則等於白定。所以三處一起改:保護清單補上、setStatus 收 until 參數(預設 +24 小時)、claimNext 抽出 claimable() 讓到期的自動回收,不需人工。

    與 escalated 的分界寫進了註解,也是這條規則的一句話版本:等得到就 deferred,等不到、需要人做決定才 escalated。

    順帶一提,同批回報裡真正成立的是另一件事:國家文化資產網是前端渲染,抓取只拿得到頁框。這條進了 backlog 但沒排程,方向是先試文化部開放資料比對而不是建常態爬蟲——目前只有 5 檔引用且都不是事實骨幹,不值得。同一條也註明神祇類那半段已被複驗推翻,免得日後有人照著錯誤結論動工。

    二、同一題寫了兩次,兩支 PR 都合併得掉

    build-article:玉皇上帝 在 08-02 就開過 PR,但任務狀態在 08-02 到 08-12 之間被掃描打回 open——in-review 是 08-12 才補進「不得重開」清單的,修補只防未來、不回溯。08-13 的 run 因而重新認領,寫出第二篇。

    為什麼檔案層的防線沒擋住?原本唯一的重複檢查是開 PR 時寫入 src/content 的同 slug 覆蓋。但 slug 由執行的 run 自取,同一個題目隔幾天再被取一次就會得到不同 slug(jade-emperor-tiangong-worship 對上 yuhuang-shangdi-no-statue),逐檔比對永遠不會相撞——兩支 PR 都開得出來、都 merge 得掉,站上並存兩篇近乎重複的文章。當時佇列狀態是唯一的防線,而它掉鏈子了。

    新的在途檢查因此刻意不依賴佇列狀態,改從兩個獨立的事實來源重建「現在有什麼在途」:一是待開 PR 記錄裡的任務 id(還沒開 PR 的),二是尚未併入 main 的 auto/* 分支(已開 PR、待終審的——開完 PR 就刪待辦記錄了,分支是唯一還活著的證據)。比對規則沿用合併收尾那支的對回鍵,不另立標準,所以抓得到「玉皇上帝:神譜裡的至高神……」這種帶副標的標題。collection 從分支變更的檔案路徑取得,不靠分支名前綴猜——auto/recheck-* 可能是神祇、文章、廟宇任一種。

    掛兩個點:取題時就擋(命中則把該任務改標成它真實的狀態 in-review,換下一筆,最多 5 次;不記帳、不佔當日槽位、排程 prompt 完全不用改),開 PR 前再驗一次當作 backstop(命中則不開、任務標 escalated 待人工定奪併稿或撤稿、草稿保留)。

    幾個刻意的取捨:依 collection 分流——同一尊神的建檔題與文章題是不同產出,不該互擋。豁免開發日誌——它的 entityRef 是時間窗而不是實體,且契約明訂「一篇一筆、同 taskId 可多筆」;第一版沒豁免,--dry 實測三篇開發日誌互相判定重複而全數卡死。fail-open——git 不可用時降級為只查待開 PR 記錄並警告,不擋題;這條防線是防重工,不值得把整條閉環卡死。

    那兩篇玉皇上帝已於同日人工併稿。

  12. 內容

    11 間策展廟是怎麼選的——把從沒寫下的準則補成明文,順手拆掉首頁的媽祖專區

    站上有 11 間「策展廟」——有專屬頁面、由人工深入撰寫的廟宇,其餘一萬多間走的是資料庫產生的頁面。查了一輪才發現,這 11 間當初是怎麼挑的,全站沒有任何文件寫過。這次把準則補成明文,往後新增哪一間有規則可循。同一批也改掉首頁精選:原本六格裡媽祖廟佔三間,首頁形同媽祖專區,現在同一位主祀神最多只佔一格,換上關聖帝君與城隍爺的代表廟。

    工程細節

    站上的廟宇分兩種:11 間策展廟有人工深寫的專屬頁面,其餘走政府資料轉出的全量頁。查證這 11 間的來歷時發現,它們其實是兩批遺留物拼起來的——8 間來自最初的版型樣本(當時的目的明寫「撐起四種頁面版型、驗證 schema 可用性」),3 間來自地府專題落地時的需要。全 repo 查不到任何收錄標準文件。

    也就是說,「要不要再加一間」這件事一直沒有依據。這次補上。

    三軌輪替,不是加權總分

    〈策展廟收錄準則〉採三軌輪替而非加權總分,理由與自治管線的取題規則同一條:內容需求是布林值、瀏覽數是個位數、覆蓋缺口是 22 縣市與 75 尊神的格子,量綱差這麼多的話,全域加權必然被最膨脹的那一軌通吃。

    輪替順序是:內容 → 結構(神祇軸)→ 內容 → 結構(縣市軸)。資料軌(依瀏覽數選)掛起,等全站月瀏覽達 2,000 再啟用。

    四條共用的 gate:G1 可寫性(至少 2 個來源、且寫得出差異點——用來擋縣市軸硬湊小廟);G2 名稱唯一性(通名會被全站自動連結誤連);G3 紅線(須為可觀測事實,不得以靈驗為由);G4 每季至多新增 2 間。

    刻意不設總量上限——看產能決定,這是使用者的定奪。但每到 25 間要重評一次節奏:沒有終點的規則需要一個回頭看的點,否則 G4 只是延緩而不是控制。

    首頁精選:媽祖佔了三格

    改版前首頁精選六格中,媽祖廟佔三間,首頁實際上是媽祖專區。政策改成「固定 6 格、每個主祀神至多 1 間、同體系內取瀏覽最高」。

    這裡有一個必須誠實交代的限制:流量目前不足以當決策依據。全站累計瀏覽只有 173 次,11 間策展廟的分佈是 6/3/3/2/1/1/1/1/0/0/0,而且全站瀏覽前兩名是「中正里福德祀」「全安宮」這種沒人會主動搜尋的登記頁——爬蟲特徵。門檻沒到之前,跨體系的取捨改以主祀廟數代打(同樣是可觀測事實,不踩 G3 紅線)。

    改版後的六格:白沙屯拱天宮(媽祖,三間中瀏覽最高)、艋舺龍山寺(觀音)、大龍峒保安宮(保生大帝)、南鯤鯓代天府(五府千歲)、台北行天宮(關聖帝君——全台第三大主祀信仰,原本竟然沒有代表上首頁)、新竹都城隍廟(城隍爺)。

    一個靜默丟資料的 bug

    首頁原本是 .slice(0, 6) 而且沒有排序。也就是說第 7 間策展廟出現時,會依檔名字母序把某一間靜默丟掉——新增一間可能無聲擠掉既有那一間,而被擠掉的是誰全由檔名決定。

    改成 build 期 fail-fast:超過 6 間、或同一主祀神重複,直接 throw。把「6 格且不重複體系」這條政策從約定變成機制。

  13. 營運 ADR 0007

    自治管線一次大調校——每日產能翻倍、既有內容納入複查、長尾建檔定出停損點

    網站每天由自動流程產出的內容題數從 4 篇提高到 8 篇,同時新增了「既有內容再查核」的機制——過去只有新寫的內容會蓋上查核日期,已經發布的舊文沒有任何機制會回頭檢查它,統計頁那個「待查 93 篇」永遠不會下降。另外替長尾神明建檔定出停損點:主祀廟數 5 間以上的神明全部建檔。這批也修掉三個讓題目「明明有卻抽不到」的隱形 bug。

    工程細節

    這批的共同主題是:佇列裡明明有東西,但取題機制讓它們出不來。

    每日 4 → 8 筆,但不是單純加倍

    起因是新增週更型別時發現無處容身——每日 4 槽 × 30 天 = 120,恰好等於月上限,任何新 run 都會把月底推到觸頂,而觸頂即全面停題。月上限因此從 120 提到 250(沿革 60 → 120 → 250),語意不變:上限 ≈ 產能,屬失控煞車而非日常閘門。實測佐證是 claim-ledger 從沒接近過觸頂(7 月用 54/120、8 月前 12 日用 31)——瓶頸長期是佇列有沒有題,不是額度。

    每日取題改成兩段式:底 4 筆照既有四條槽位規則各取 1,配比保證與舊制相同;頂 4 筆是遞補額度,順序為今日輪值 → 次一輪值 → 神祇建檔 → 再查核 → 產圖 → 全域分數排序(最後手段)。

    兩條硬護欄:任一型別當日至多 3 筆、全域分數排序只在前五順位全數落空時才動用。≤3 這條是防止遞補被單一型別整碗端走——不設限的話,佇列最深+計分最膨脹的型別會吃光頂 4 筆,等於從側門把「為何不比分數」那條原則放回來(build-article 當時已佔實際取題 33%、open 52 筆)。

    為什麼不用純輪值:--type 取不到會回 null、exit 0、不記帳也不耗額度,所以共用制撞到空佇列只是多一次 CLI 呼叫;純輪值則是空槽直接損失。實測 calendar-feast 當時 0 open,而輪值表每週給它 5 個槽——8 槽制下每週會有約 10 槽空轉,旁邊卻有 256 筆積壓動不了。

    ⚠️ 誠實記一筆:≤3 是 prompt 層規則(run 知道自己當日取過什麼),不是程式強制。要硬化的話 next-task 加一個 --exclude-type 旗標即可,這次沒做。

    既有內容從來沒人回頭看

    reviewStatus/lastVerified 三欄落地之後,只有新產出的內容會經 post-merge 蓋上查核日,既有內容沒有任何 scanner 看得見——/stats/ 的「待查 93 篇」是個永遠不會下降的數字。新的 recheck-content scanner 掃三個 collection 的 frontmatter,撈出從未查核或超過週期(知識 24 個月、廟宇 12 個月)的篇目成題;未查核者給 1000+ 分、逾期者以逾期天數計分,確保積壓先清完。

    這裡有一條刻意的設計:查核無誤也要出 PR。lastVerified 的全站唯一寫入點是 post-merge 對 ai-audited 的翻面,所以即使一個字都沒改,也要出一支「只差 reviewStatus 一行」的 PR。不得為了省事直接改日期——那等於 AI 自己給自己蓋查核章,破 ADR 0007 的鐵則。

    長尾建檔終於有停損點

    「視覆蓋率目標再開批次」讓批次無從收斂,而門檻只存在於某支腳本的預設值裡、不是明文契約。這次定案:主祀廟數 ≥5 的神明全建檔(全建完覆蓋率上限 82.8%)。

    但降到 5 之後浮出一個新問題:長尾裡「既有檔的變體寫法」與「真的沒建檔」混在一起,掃描器不分流的話,自治迴路會替既有神祇建出重複檔案。實測長尾前段多數不是缺建檔,而是既有檔漏收政府登記資料的變體——補別名即可歸戶,成本遠低於建檔。光是替觀世音菩薩補 3 個別名、玄天上帝補「北極玄天真武上帝」、中壇元帥補「三太子」、釋迦牟尼佛補「釋迦摩尼佛」(政府登記的錯字),ETL 命中就從 9,715 跳到 9,796,覆蓋率 75% → 78.9%。

    新的 deity-alias-match.mjs 以異體字正規化+包含關係判定別名題。刻意不採編輯距離當判定依據——中文神名差一個字通常是不同的神(金府千歲/池府千歲、三奶夫人/陳奶夫人、地母娘娘/王母娘娘),實測 68 筆長尾誤判率約五成,故降級為建檔題的參考提示而非自動判定。

    三個讓題目消失的 bug

    一、覆蓋判定太寬,28 個大神被自己人誤殺。 文章缺口與節慶兩支 scanner 的覆蓋判定都串接全文(含 frontmatter、sources、HTML 註解)做 includes,語義是「站上任何一篇的任何角落提過這個詞」——但成題要問的是「有沒有專篇」。內文順帶提一句不等於寫過,而且一篇寫得越完整,擋掉的題越多。實測 105 檔神祇在全文判定下 46 檔被擋,改標題判定只剩 18 檔——玉皇上帝、地藏王菩薩、城隍爺、註生娘娘、中壇元帥全在誤殺名單裡。節慶題 16 筆有 9 筆死、其中 7 筆是誤殺(天公生與燒王船這種基礎分 150 的大題)。修法是判定語料改為各篇 title 串接,並加一個 coveredBy 宣告欄位——覆蓋與否屬編輯判斷時用宣告的,優先於文字比對。

    二、已開 PR 的任務被 scan 重設回 open,差點產出重複 PR。 upsert 的狀態保護清單是 done/dismissed/escalated,in-review 不在內。而再查核 scanner 看的是 lastVerified,該欄要等 PR merge 後才寫入——PR 未 merge 期間必然再次成題,於是被重設回 open 並可被再次認領。當日實測 6 筆 in-review 於 scan 後全數歸零,使用者手動取題時取到一筆當天 06:15 已經開完 PR 的。修法是清單補 in-review;in-progress 刻意不納入——run 中途夭折時需要靠 scan 放回 open 才能重取,且該狀態下還沒有 PR 這種實體產出,重做成本低。

    三、一個還沒關的洞。 兩段式取題規則寫進了 prompt 版控副本,但 Cowork 排程 UI 上的 prompt 需要人工同步一次,否則排程實跑仍是舊制 4 筆,月上限提到 250 等於白改。

  14. 功能 ADR 0011

    「想求什麼」變成一種找廟的方式——祈求標籤雙層上線

    從今以後可以反過來找神:想求姻緣、想求學業考運,站上會告訴你該找哪幾尊神、哪幾間廟。神祇頁列出「這尊神管什麼」,廟宇頁則用三級色階標出這間廟的香火焦點,每一條都附得出理由。比較費事的是決定「記什麼」——我們不記哪間廟比較有效,只記「人為什麼來」:廟方設了什麼殿、辦了什麼法會、點哪幾種燈。這是可以查證的事實,前者不是。

    工程細節

    這個功能在 backlog 躺了整整兩個月(自 2026-06-12),原始描述只有一句「神祇主責保佑 tags」。真正卡住它的不是工,是不知道該記什麼。

    記錄單位:不是效驗,是香火焦點

    使用者最初的描述是「檢索網路上這間廟求哪件事比較靈」。照字面做會撞上自己的護欄——EDITORIAL.md 的 T3 四類題材第四類明列廟際效驗比較,t3-triggers.mjs 的字面詞表也收了對應的詞;自治管線寫出這種句子會被攔下、標 escalated。護欄擋得對,但功能本身有價值,所以問題變成:同一個需求,換一種記法。

    ADR 0011 的決定是把記錄對象從「效驗」換成「人為什麼來」:

    不寫改寫成
    某間廟的月老特別有效龍山寺設月老殿,是台北香客求姻緣的主要對象之一
    求財首選某廟紫南宮提供借發財金服務,每年正月排隊人潮見諸報導
    這間廟安太歲特別有效這間廟辦理安太歲,太歲燈為其主要服務品項

    差別在可查證性與歸屬:右欄是社會事實與廟方自陳,附得出來源、標得了信度;左欄是效驗斷言與廟際比較,查不到也歸不了責。

    換完之後,主訊號跟著換了——不再是網路輿論,而是廟方自營的服務:設不設月老殿、文昌殿、太歲殿,辦不辦考生祈福,點哪幾種燈。這些是物理事實,官網查得到,而且與祈求項目幾乎一對一。網路討論降為第二順位弱訊號,只採現象描述(考季人潮),不採排行榜式內容農場文。

    本站早有同型前例:媽祖臉色那篇刻意不採媒體的效驗式標題(記在 deity-factcheck-tracker.md)。ADR 0011 只是把該次個案判斷升格成通則。

    權重軸線:香客會不會為了祂單獨來一趟

    三級證據強度:級 1 是廟宇層實證(廟方設殿/辦法會/自陳服務)或主祀神職司,級 2 是同祀神職司,級 3 是配祀與從祀。

    同祀排在配祀、從祀之上,理由是獨立香火而不是神格高低:同祀依 ADR 0004 的定義與主神無從屬關係、常在偏殿各有香火,實務上多半獨立設殿(龍山寺月老、霞海城隍廟月老殿都是同祀);從祀是主神的部將隨侍,沒有人專程去求千里眼順風耳。

    這條軸線與 ADR 0004 的四分法不同軸但不衝突——0004 談宗教從屬關係,這裡談的是「香客會不會為了祂單獨來一趟」。實作時特別註明:不要把四分法順序當權重直接套用。

    疊加規則是取最大值,不累加。一間廟能不能求姻緣,取決於有沒有一尊夠份量的姻緣神,不是有幾尊;累加會讓配祀眾多的大廟每個標籤都滿格,訊號被洗成噪音。

    神祇層 108 檔:判讀,不是研究

    神祇層的工作性質常被誤判成「要重新研究每尊神管什麼」,實際上不是——108 檔全部都有人工寫過的 perspectives[].role(月老是「姻緣牽線」、保生大帝是「醫神、濟世救人」、床母是「護佑嬰幼兒安睡好養、平安長大」)。標籤只是把既有主張結構化,所以來源沿用該檔既有的 sources,不另補。

    五條判讀原則:依據限於 role;role 沒寫的不標;教義位格不等於祈求項目;從祀脅侍留空;行業守護走另一個欄位 patronOf。

    結果是有標籤 75 檔、留空 33 檔、有 patronOf 15 檔,合計 116 個標籤。留空是設計上的合法狀態而非遺漏:佛教尊格 10 檔(阿彌陀佛的 role 是「西方極樂世界教主」,標成祈求項目等於把解脫論降格為民間祈願,違反 EDITORIAL 那條「不評斷正信與習合何者正確」)、神話位格 15 檔(盤古、共工、三清)、從祀脅侍 5 檔、待補來源 3 檔。反例是女媧照標「姻緣」——它的 role 明載「婚姻高禖」,神話神並不自動留空。

    判讀理由集中寫在 deity-blessing-worksheet.md,108 尊逐一列出提議、依據與決定,分五批(生活神/境主王爺地府/文武行業/佛教尊格/神話位格)——同類一起判,標準才會一致,王爺系 12 檔因此統一標「平安」加「解厄化煞」。使用者定奪時明寫 OK 54 列、留空視同 OK 52 列、覆寫 2 列(福德正神加「財運生意」並移除「農作豐收」;太陰娘娘由留空改為「求子」與「姻緣」)。這兩筆超出 role 記載範圍、屬民俗實務的定奪,md 裡逐條註了「待補來源」,再查核時要優先補上出處。

    詞彙表定成 14 項封閉 enum,順帶得到一個免費檢查:每一項都必須有至少兩尊對應。某項 0 尊就代表詞彙表定錯了。實測 14 項全數命中,分布從「解厄化煞」29 尊到「求子」3 尊。

    三級色階,但色彩不能單獨承載級別

    廟宇頁的標籤用三級色階呈現,這裡有一條硬性的無障礙要求:級別不能只靠顏色表達。做法是級 1 實心深、級 2 實心淺、級 3 外框淡——填滿與否是第二通道,灰階列印與色覺障礙下仍分得出來;另外補 aria-label 帶級別文字。

    神祇頁則刻意沒有級別:「這尊神管什麼」沒有強弱之分,強弱來自祂在某一間廟的奉祀角色。點擊標籤會帶 ?blessing= 跳到 /deities/ 的對應篩選,而那排篩選按鈕只列實際有神祇對應的項目——空篩選不該出現在 UI,這是前述那個免費檢查的介面版本。

    元件本身不重算疊加,級別由 ETL 算好落庫、元件只負責畫。同理,兩種廟宇頁(策展頁走 build 時 sqlite、全量頁走 runtime D1)共用 ETL 算好的同一份結果,不各自實作——否則兩處邏輯必然漂移。

    廟宇層:覆核剔除的 9 條比採用的 30 條更有價值

    11 間策展廟由四個平行研究代理查廟方官網與政府資料,產出 39 條候選,人工覆核後採 30 條。官網 403 或 robots 阻擋的改採內政部宗教文化資產、文化部國家文化記憶庫、中研院 CRGIS、地方政府等來源。

    被剔除的 9 條裡,最常見的型態是純奉祀——「後殿奉祀神農大帝」「廟內配祀註生娘娘」「主祀城隍爺,職司賞善罰惡」。這類敘述正是神祇層推導的輸入,寫進廟宇層等於重複計算,還會把本該級 2 的標籤誤升成級 1。門檻與對照表因此寫進規格 §3.2,並同步到 content-loop 與週更 prompt——自治管線抄格式時會一併抄到這條。

    另外剔除的四類也各自立成規則:消防局合辦的防火宣導遶境(公共安全活動、非信眾祈求主題,且是 2021 單次);開爐選拔迎分靈回家(分靈活動,與家宅安寧關聯過弱);官網被 robots 擋住、只讀到搜尋引擎索引的頁面標題沒讀到內文的那條(只讀到標題不引用);唯一來源是「多廟點燈懶人包」體裁財經媒體文的那兩條(新聞層級只採現象報導)。

    研究代理自己主動剔除的也值得記:中央社報導的月老參拜指南經查是第三方市集主辦、非廟方;親子論壇那句「太靈了」屬效驗語氣;各種「全台○○必去 10 間」排行文與命理業配全數排除。南鯤鯓的發財金只寫辦理方式(每份 600 元、具名請領),廟方文宣裡的招財說法一概不引。

    ETL 那一段踩到的三個坑

    疊加照規格五步走:蒐集全部貢獻(廟宇實證恆為級 1 並帶 evidence,每尊奉祀神祇依 role 判 1/2/3 並帶 via_deity)、取最強、origin 取該級贏家、evidence 與 via_deity 各自獨立填該類最強者、不因誰贏而清空另一欄。「同級時廟宇層優先」不另寫分支——讓廟宇層先跑佔位,神祇迴圈用嚴格小於比較就搶不回來。同級多尊的排序必須是決定性的,否則 hash 快取的有效性判斷會失真。

    三個坑:

    1. 策展 md 寫 slug、overrides yaml 寫神名,而索引只有「神名→slug」單向。 直接把 md 的 enshrines 丟進共用迴圈,deity_slug 會整批落 null——列有進庫、join 不到神祇頁、build 照過。解法是先正規化回正名再交給共用迴圈。
    2. 非 .strict() 的 schema 會靜默剝除未宣告欄位。 blessings 必須跟 enshrines 一樣從 override patch 解構抽出再 assign,否則資料寫了等於沒寫。這是本專案第四次踩同型坑(同一批的 schema 加欄也記了一次:先加欄再寫資料的順序不能反,zod 對未知欄位預設是 strip 不是 error)。
    3. 14 項 enum 在 content.config.ts 與 ETL 各有一份副本(ETL 跑在 Astro 之外,匯不到虛擬模組)。補了 assertItem 在 build 前擋下兩份不同步。

    另外新增 5 條 fail-fast,最後一條特別說明:「策展編號出現在 overrides 層的 blessings」是無條件檢查。原本寫成「md 也有 blessings 時才比對」,於是自治產出蓋在旗艦廟上時會靜默生效。seed 排除清單只是約定,這條才是機制。

    一筆延後生效的帳

    首批 7 間策展廟落地時 ETL 還沒重跑(掛載區的 better-sqlite3 是 macOS binding,Linux VM 載入失敗),temple_blessings 當下仍是舊的 15,457 列、evidence 0 筆。收尾那 4 間在 Mac 端實跑 ETL 後才真正驗證通過:14 項 enum 全數命中受控詞彙、evidence 必填皆有值、策展 md 帶入的 30 條全部落庫。

    這一批同時讓 origin='temple' 這條程式路徑首次有 committed 資料會走到——先前只有注入 fixture 驗過,repo 裡沒有任何真實資料經過它,等於沒有回歸保護。

  15. 內容

    鬼門開前一天上線——鬼月特輯與那篇不談禁忌的鬼月文章

    農曆七月前一天,站上補齊了一整組鬼月內容:一篇從文獻考據切入的〈鬼月禁忌的來歷〉、地府專題頁的時效區塊、首頁的鬼月導引橫幅,以及分享到社群時會顯示的專屬預覽圖。這篇文章刻意不寫「鬼月三十條禁忌」——市面上那種太多了,我們改去查「鬼月」這個詞到底什麼時候出現的,結果發現歷代歲時文獻裡根本沒有它。同時新增了大士爺(普渡場正中央那尊鬼王)的神祇條目與神像圖。

    工程細節

    年輕族群內容策略的第一層是「進得來」,而鬼月是一年最大的時效窗口。問題是「鬼月 30 條禁忌」是紅海——同格式打不贏媒體站的 SEO 權重,做了也只是多一篇沉底的文章。

    所以〈鬼月禁忌的來歷〉改以考據切入。查下去發現的東西比預期有意思:農曆七月的傳統名稱是巧月、瓜月、蘭月,歷代歲時文獻裡沒有「鬼月」這個詞。沈復《浮生六記》記的是「七月望,俗謂鬼節」——是一天不是一個月;張岱《西湖七月半》記的七月半西湖遊人如織,完全不是禁忌氛圍。而「朱元璋造謠說」流傳最廣的那個關鍵證據「古來七月天子葬」,實出《禮記·王制》的「天子七月而葬」——指的是死後七個月的喪期,不是農曆七月,且《明實錄》所載明代皇帝幾乎沒有七月下葬的。

    台灣線索則落在基隆:1853 年魴頂械鬥後合葬的老大公,與 1855 年起的雞籠中元祭。順帶點出老大公廟的儀式正名是「開龕門」,口語的「鬼門開」是後來疊上去的。

    文章另提出一個本站自己的分層觀察:禁忌可以按成立前提分三個年齡層,第三層(不買車、不搬家、不簽約)以「禁忌不可能比它禁止的對象更老」的邏輯論證其年齡上限。鬼月形成的兩說(台灣移民社會生成/宋代三教合流深化)依 EDITORIAL 的矛盾並存原則並列不裁決。全篇不採「破除迷信」姿態,結尾明寫不評斷該不該遵守——這條線很重要,考據不是拿來拆信仰的。

    時效內容的工程問題:怎麼提早掛上、怎麼自動撤下

    原本的判斷只認「今日落在農曆七月」,但要在鬼門開前一天上線的話,當天 build 會判 false、整段不渲染,等於白部署一次。所以 ghostMonthWindow() 加了 leadDays(預設 3)與 phase 欄位:active=七月當月,lead-in=初一前三天內並回傳倒數天數。「明天鬼門開」的搜尋熱度在開月前就起來了,提前掛上吃得到。天數差改走 UTC 日界計算,避開時區與日光節約造成的 ±1 偏移。

    首頁橫幅是同一天補的。鬼月區塊原本只做在 /realms/underworld/,首頁三個區塊完全沒有指向它的連結——訪客得先注意到導覽列有「地府專題」並主動點進去。對一個只有 29 天的時效內容,入口藏得太深,等於留存做好了但流入沒做。 橫幅與地府專題頁共用同一個 ghostMonthWindow(),null 時整段不渲染,過月自動撤下,不需要記得回來拆。

    兩個踩雷

    一、少打一個 >,兩支產圖腳本全死。 加鬼月 OG 卡模板時用 const mimeOf = (file) => 當定位錨點,替換字串結尾誤寫成 (file) =,把箭頭函式切成賦值表達式。node --check 通過——(file) = expr 在語法層合法,只有執行期才 ReferenceError,直到使用者實跑才暴露。修完立了規矩:往後改 .mjs 一律以實際 import() 驗證並列出匯出,不能只靠語法檢查。(本月第二次「用某行當錨點結果把它吃掉」,另一次見同批的祈求標籤篇。)

    二、社群平台發舊圖。 OG 圖換了但 og:image 網址沒變,快取拿舊的;補上內容雜湊解決。

    題庫與大士爺

    順手發現一件事:整個農曆七月,題庫裡只有〈中元普渡〉1 筆題,而且它的 match 詞是「中元/普渡」這種通用詞,早被多篇既有文章命中而永遠不會被抽到。實測當時佇列現況——鬼月期間自治管線可取的題只剩修死連結、神祇建檔與產圖,跟節期完全無關。所以補了 6 筆延續考據取向的題:豎燈篙、放水燈、大士爺與普渡場、跳鍾馗、搶孤、鬼門關與地藏聖誕。每個 match 詞都先以全文語料實測未命中才採用,並把「須選只有寫了這篇才會出現的專屬詞」這條選詞紀律寫進檔頭。

    大士爺(面燃大士)則是這批的意外收穫。使用者原本要求首頁橫幅配「惡鬼圖」,改以大士爺處理——祂是普渡場正中央那尊鬼王,視覺張力足夠(口吐火焰、頂髮煙生),但身分是受觀音度化的神格,畫面重心在「鬼王頭上頂著一尊觀音」的並置而非刑罰血腥,避開了 EDITORIAL〈地獄與惡鬼內容守則〉與 T3 的地獄獵奇邊界。神像圖產出後補了神祇條目:三層 perspectives 並列(佛教焰口鬼王/道教普渡真君/民間大士爺),身分兩說並列不裁決,並點出兩說共通的造像關鍵——頭頂必立小觀音。

    feastDay 刻意留空:大士爺沒有聖誕,祂是普渡期間才現身的神。民雄祭期寫進正文而不塞進機器欄位。敘事主軸取「一年只出現一個月的神」——多數廟宇沒有常設神像,每年紙紮誕生一次、同月火化一次,台灣獨一份。

  16. 功能

    文章網址搬家——/articles/ 一次性遷入知識庫,舊連結自動轉址

    文章的網址從 /articles/文章名 搬到 /knowledge/分類/文章名,網址本身現在就說得出這篇文章屬於哪個主題;24 篇舊網址全部設好自動轉址,外部既有連結一個都不會斷。同時每篇文章補上發布日期,「最新文章」從此是真的最新,訂閱 RSS 也看得到發布時間;新增「全部文章」一頁可以從新到舊一次瀏覽。

    工程細節

    這是 2026-06-12 知識庫規格裡就埋下的「URL 分歧記錄」的結案:規格書 4.1 一直定義文章頁該住在 /knowledge/{category}/{slug}/,但 milestone-1 實作在 /articles/{slug}/,當時決定等正式網域上線再一次性遷移、避免破壞連結兩次。myths.tw 上線後這個前提到期,而且每多等一天,舊址被搜尋引擎索引、被外部分享的量就多一天——轉址債只會變厚,所以趁早結案。

    轉址是這次的核心工程。Pages _redirects 的 :slug 佔位符查不到 frontmatter 的分類,一條 splat 規則組不出目的地,只能逐篇列舉。好在集合天然封閉:遷移日之後出生的文章(content-loop 每天還在產)從未存在過 /articles/ 網址,不需要轉址——所以產出的是「凍結快照」:當日 24 篇 × 含/不含斜線兩行(保證一跳到位、不吃雙重轉址)+列表頁 2 行,共 50 條規則,產生器 scripts/migrations/gen-articles-redirects.mjs 入版控留痕,區塊本身勿再增修。

    全站組址收攏成一個函式。原本文章連結是五、六處各自手拼 /articles/${id}/ 字串;現在統一走 articleUrl(id, category),住在 src/lib/knowledge-categories.mjs——原本是 .ts,改成 .mjs+d.ts(仿 feast-parse 慣例)是因為 remark-wikilinks 在 build 期跑在 astro.config 的載入鏈裡,也要組文章網址。未知分類直接 throw,build 期 fail-fast,抓「enum 加了新分類但固定表沒跟上」的漂移。清單頁採乙案:/articles/ 升格為 /knowledge/all/ 全部文章平面視圖,文章宇宙從此收攏在單一 namespace。

    順手補齊了拖很久的日期缺口。articles schema 一直沒有日期欄位,「最新兩篇」其實是檔名字母序。這次加 publishedAt(optional——必填會炸 content-loop 既有草稿契約),24 篇舊文以 git 首次 commit 日回填(scripts/migrations/backfill-published-at.mjs);總覽預覽、分類 hub、all 頁、RSS 全部改吃此欄。RSS 同時把 guid 改成文章 id(isPermaLink=false):識別與網址從此脫鉤,這次訂閱者會看到整批重複一次(link 全換無可避免),但下次再改址就不會了。

    架構代價要記住:分類進了網址,改 category 等於改永久連結。對策寫進 EDITORIAL——已發布文章不改分類;真要改的個案,_redirects 補一條舊址轉新址。驗證方式也記一筆:本機 VM 沙箱跑不了完整 build(better-sqlite3 是 Mac 二進位),這次改在雲端 Linux 沙箱 npm ci 原生編譯後跑完整 npm run build——dist 24 篇新路徑齊、殘留舊連結 0(html/js/pagefind 全掃)、sitemap/RSS 皆新址,回寫後 48 檔 md5 與驗證樹逐位元一致。互動分析零影響:計數鍵是 (type, id) 不是路徑,累計瀏覽數完整保留,推薦欄存的舊 url 會隨瀏覽 upsert 自癒。

  17. 品牌

    「雙嶼日出」上任——品牌標記進了導覽列與瀏覽器分頁

    小島神話終於有了自己的圖案。設計系統裡定稿的標記「雙嶼日出」——朱砂的雙島、金色的日與海線——正式放進網站左上角的站名旁邊,也做成了 favicon,加入手機主畫面時不再是一個空白預設圖示。首頁的分頁標題同時改成完整的「小島神話 Mythsland - 紀錄島嶼的眾神諸佛」。

    工程細節

    站名從 miao 改成小島神話/Mythsland 是 7 月中的事,但改名之後有一段時間,網站的視覺識別只剩下文字——導覽列是純文字站名,瀏覽器分頁是預設的白紙圖示,加入手機主畫面出來一個灰塊。設計系統規格書(docs/design-system/)這邊已經把標記定稿了,缺的只是落地。

    BrandMark 元件。新增 src/components/BrandMark.astro,幾何完全依規格書。配色用的是深底版本——朱砂提亮一階到 cinnabar-400、金日 gold-300、海線 gold-400,因為導覽列是深色底,直接套淺底版的朱砂會糊掉。尺寸 30px(規格書指定的頁首尺寸),置於站名左側。順手把 brand 區塊改成 flex 置中對齊,同時讓站名文字與 tagline 維持 baseline 對齊——這兩件事要同時成立需要一點克制,圖標垂直置中、文字水平基線對齊,不能只用一個 align-items 打發。

    favicon 與 apple-touch-icon。依規格書第柒節(宣紙底、圓角、標記佔畫布 72%)產出 public/favicon.svg 與 180×180 的 apple-touch-icon.png。PNG 是用 Playwright 從 SVG 轉出來的——已經為了手機版檢查裝了 Playwright headless Chromium,拿它當一次性的 SVG 光柵化器,比為此再引入一個圖形處理相依划算。BaseLayout 與靜態的 public/404.html 兩處 head 都補上 icon 與 apple-touch-icon link;404 頁是純靜態不走 layout,很容易漏。

    首頁標題的一個小 prop。首頁的 <title> 原本是「首頁|小島神話」,作為 SEO 與社群分享的標題太弱。想改成完整站名加標語,但 BaseLayout 一律會補「|小島神話」尾綴,直接傳完整標語會變成疊字。做法是給 BaseLayout 加一個 titleSuffix prop(預設 true,其餘頁面行為不變),首頁傳 false 時直接使用傳入的 title。這種「預設維持舊行為、例外才 opt out」的加法,是改共用 layout 時最不容易誤傷的形狀。

  18. 營運 ADR 0007

    站務儀表板改頁籤,並在頁尾埋一道只有站長看得見的門

    站內有一頁 /ops/ 是給站長看的「工程進度」——哪些內容該補、哪些神像卡在哪一關、哪些查核還沒做。這次把它從一路捲到底改成四個頁籤,並新增一張神像產製的總覽表。同時在每一頁的頁尾預埋了一個入口,只有站長自己的網路連進來時才會顯現,其他訪客與爬蟲完全看不到。

    工程細節

    /ops/ 是自治閉環的觀測窗:機器每天自己取題、產內容、開 PR,人只做終審,那麼「現在整體長什麼樣」就得有一頁看得到。它原本是四大區塊縱向堆疊,內容一多,看第四塊要捲很久。

    頁籤化。指標 strip 之下拆成四頁籤——優先序建議、backlog 進度、內容查核佇列、神像產製進度。實作用 ARIA tablist,配 hash 深連結(#tab=<key>)讓某一籤可以直接分享或加書籤,方向鍵可循環切換,並監聽 hashchange 讓同文件內的導航(不重載頁面的那種)也能正確切籤。順手拿掉 lead 裡硬編的「對照」句——backlog.updated 本來就自帶,先前是重複顯示。

    神像產製表。這是新增的第四籤,解的是「每一尊神像現在卡在哪」沒有總覽視角的問題。神像閉環的狀態不是單一欄位能表達的:work-queue 裡的 build-deity-image 任務有六種狀態,但同一個 in-review 底下,實際可能是「等使用者產圖」「母檔已收件等後製」「後製報告卡人工」「已開 PR 等終審」好幾種處境。src/lib/image-pipeline.ts 的做法是拿佇列任務跟 deities collection 交叉比對,再依落點檔案存在與否推導子階段——pending-tg、image-intake 底下的 original/cleaned/POSTPROCESS-REPORT、pending-pr、public 下的 webp,逐層往下推。排序刻意不按字母,按「該理誰」的順序:escalated → 待產圖 → 待終審 → 後製卡關 → 機器在途 → open → 未入佇列 → dismissed → done → 既有圖。佇列檔缺席時回 null 顯示「無佇列資料」,沿用雲端 build 缺檔降級的先例,不讓一個缺檔炸掉整頁。手機上這張表在容器內橫捲。

    頁尾的隱形入口。全站是 SSG,頁尾是 build 時就定死的靜態 HTML,沒辦法在伺服器端按訪客 IP 出不同內容。所以做法是一個執行期的判斷點:新增 GET /api/ops-gate(SSR,prerender=false),讀 CF-Connecting-IP 比對站長 IP 白名單,回 {ops: boolean},並帶 Cache-Control: no-store 防止這種因人而異的回應被快取住。頁尾在「開發日誌」旁邊預埋一個 hidden 的「工程進度」連結——初始 HTML 就是隱藏的,非白名單的 client 從頭到尾不會被揭示,爬蟲也抓不到。client script 先查 sessionStorage 快取(mt:ops-gate),沒快取才打端點,回 true 才揭示;任何失敗一律靜默,沿用互動分析 track-client 的旁路原則——輔助功能壞掉不該讓使用者感覺得到。dev 環境一律回 true,與 /ops/ 頁本身 dev 一律可見保持一致。

    要說清楚的是:這個入口是便利功能,不是防線。IP 白名單能被偽造、hidden 屬性能被翻出來,/ops/ 真正的保護仍然是 Cloudflare Access。規格 §3 把這個定位寫進去了,免得日後有人以為這道門擋得住誰。IPv6 的邊角情況(站長從 v6 位址連入不在白名單內)也一併記了。

  19. 內容 ADR 0007

    自治閉環進入滿速——四天併入 19 筆自動 PR

    站上的內容現在有一條穩定的產線:機器每天自己找出缺口、查資料、寫草稿、開一個待審的 PR,人只做最後一道把關。這四天累積併入 19 筆——8 篇文章、7 尊神像、3 筆節慶行事曆,外加 1 篇開發日誌。同期也把產線上兩處還要人工介入的地方補成自動。

    工程細節

    自治閉環(ADR 0007)從 7 月 16 日上線,前一週還在補管線;每日取題上限在 7 月 23 日從 2 筆調到 4 筆之後,產出量才真正跑起來。7/23 到 7/26 這四天,併入 main 的自動 PR 共 19 筆:

    • 文章 8 篇:釋迦牟尼佛、讀懂媽祖神像、讀懂觀音神像、讀懂玄天上帝神像、讀懂關帝神像、五府千歲怎麼認、瑤池金母與台灣母娘信仰、讀懂保生大帝神像
    • 神像 7 尊:五福大帝、五路財神、五年千歲、三官大帝、三府王爺、五顯大帝、伏羲
    • 節慶行事曆 3 筆:有應公、西方三聖(大勢至菩薩聖誕)、五福大帝五靈公五列
    • 開發日誌 1 篇

    每一筆都經過 AI 草稿、AI 稽核、人工終審三道。要強調的是第三道沒有被自動化,也不打算自動化——發布權留在人手上是這個閉環的前提,機器省掉的是查資料與寫初稿的重複勞動,不是判斷。

    Stage 4b:母檔歸檔補成自動。神像閉環有一段一直是人工的:使用者產出的圖檔落在 Downloads,收件處理完之後要手動搬到 ~/Pictures/miao-神像母檔/閉環產出 歸檔。7/21 一次性搬遷過一輪,然後立刻又積壓三張——人工步驟在每日節奏下不可能不積。這件事有實質風險:macOS 的 Downloads 有 30 天自動清除,而 R2 備份只涵蓋 Pictures 目錄,等於母檔放在一個會自己消失又沒被備份的地方。

    image-bridge.mjs 補上的做法:收件成功後原檔自動搬入閉環產出,並 best-effort 追加一列 provenance README 索引(讀 PNG 的 IHDR 取尺寸、md5 去重);已經收件但仍留在收件匣的同內容舊檔也補歸檔。跨磁碟搬移失敗時退回 copy 加 md5 校驗。歸檔遇到同名舊母檔,改名為 <名>.v<日期>.png 保留——永不覆寫,母檔是不可再生的東西。母檔目錄不存在時只警告不歸檔,不中斷收件。

    同時修掉一個靜默的坑:原本的 hasIntake 規則會讓已收件的檔案永遠留在收件匣,而重新產圖的同名新檔會被永遠略過——也就是「這尊圖重畫了」這件事管線根本看不見。判準改成:同名但 md5 不同、且任務在 in-review,即認定為重收件,把舊的 intake 目錄整包移到 data/image-intake/.superseded/<slug>-<時戳>/ 再重走一次收件。要連 POSTPROCESS-REPORT 一起移走,否則沙箱的 Step 0 會讀到舊報告,判斷「已處理過」就跳過不重做。這種「舊產物讓新輸入被靜默略過」的形狀,在任何有中間檔的管線裡都會反覆出現。

    通知分流。閉環跑滿速之後,Telegram 上部署通知與內容通知混在同一個 topic 裡,部署狀態被內容訊息淹掉。notify.mjs 加了 deployThreadId 設定位:TG_THREAD_ID 的語意改為「deploy 以外所有通知的預設 topic」,deploy 自帶 TG_DEPLOY_THREAD_ID 覆蓋留在原 topic。deploy.mjs 四個通知點(kill-switch/前置失敗/完成/失敗)全部帶上覆蓋。產圖通知則反過來併回一般 topic,image-bridge 裡「見產圖 thread」的文案跟著失真,一併拿掉。deity-image-loop.md 規格裡「發布位置=獨立 thread 5」的舊條目用劃線保留沿革、下面補記現況——規格改動留下歷史痕跡,比直接覆寫更能解釋「為什麼會變成現在這樣」。

  20. 功能 ADR 0007

    全站錯誤回報上線——每一頁都有一顆香火圓鈕

    站上的內容有錯字、記載可議、或哪裡壞掉了,現在讀者可以直接說。每一頁右下角都有一顆香火色的圓鈕,點開就能填問題、拖一張截圖進去送出,不必註冊、不必寫信。三天後補上選填的稱呼與 Email,願意留的人我們才有辦法回覆。

    工程細節

    一個以事實查核為賣點的站,最怕的是錯誤沒有回頭路。神明聖誕差一天、敕封朝代寫錯、廟宇地址搬過家——這些只有真正在拜的人會發現,而他們發現的當下若沒有一個「兩秒鐘就能講」的入口,這個發現就流失了。錯誤回報 V1 要解的就是這個。

    收單管線。POST /api/report 的處理順序是刻意排的,攻擊面在入口一次性擋掉:Origin 白名單(只收 myths.tw、miao.test、127.0.0.1,pages.dev 與 preview 刻意不收)→ bot UA 過濾 → 總量粗篩 → Turnstile siteverify → 欄位消毒 → 截圖 magic bytes 複驗 → 生 ULID → 寫 R2(副檔名依實際嗅探結果重寫,不信任使用者給的檔名)→ 寫 D1 → 發 Telegram(best-effort,通知掛掉不影響收單)。資料落在兩個獨立資源:D1 miao-feedback 存單、R2 私有桶 miao-feedback 存截圖,都不與正式內容庫混用。

    前端的成本控制。BaseLayout 注入的是純 HTML/CSS 的圓鈕,首載零 JavaScript、不動渲染路徑;點擊當下才動態 import modal 與 Turnstile。表單支援三種上傳途徑(拖曳/選檔/貼上),前端先做一次 magic bytes 初驗擋掉明顯偽檔,後端再驗一次——前端那層是省流量,不是防線。

    三天後的規格放寬。V0.1 刻意 YAGNI 不收聯絡資訊,實際上線後發現:收到「這裡怪怪的」但無法追問細節,等於收到一半的情報。於是 v0.3 補上稱呼與 Email 兩個選填欄,配套是三態消毒語意——null 代表未填(照收)、有值代表格式合格、undefined 代表填了但不合格(拒單)。選填但填了就要對,避免資料庫裡躺著一堆打不通的地址。回信流程本身推遲到 V3 再議。

    兩個踩雷,照實記。上線三天後修掉兩個都是自己造成的問題。其一,短頁面的回報鈕永久隱形:頁尾淡出用 IntersectionObserver 判斷 footer 是否進入視窗,但像大甲鎮瀾宮、/knowledge/ 這類內容不足一個視窗高的頁面,footer 從一開始就在視窗裡,鈕一載入就被判定該淡出——修法是淡出邏輯只在可捲動距離超過 240px 的頁面啟用。其二,神明頁的回報鈕懸在半空中:首頁的行事曆浮牌會把鈕往上抬,抬升邏輯用 .feast 類名找浮牌,結果神明頁的聖誕資訊行也叫 .feast,抬升量拿內文元素去算,桌機硬是被推高 602px——修法是選擇器改鎖 .feast[data-feast],用浮牌本體獨有的屬性。兩個都是「靠類名找元素」的老問題,在一個元件散落全站的功能上會放大。

    還有兩項是機器做不了的人工待辦,登記在 docs/MAINTENANCE.md 裡等處理:Turnstile widget 的正式設定,以及 WAF 規則要擴到回報路徑。V2 的分流與 V3 的審核台都還沒動——目前收到的單是靠 Telegram 通知人工看。這是刻意的順序:先讓話有地方講,再想怎麼把話整理好。

  21. 營運 ADR 0007

    自動開 PR 橋接補上節慶資料型別

    站點的內容自動化再進一步——節慶行事曆(民俗節日)的資料現在也能由 AI 產出、自動開 PR 送人工審核,成為繼神祇、文章之後第三種走自動流程的內容。同時審核通知改成每筆直接附上 PR 連結,終審者少一次翻查。

    工程細節

    自動開 PR 橋接(open-prs)是把 AI 在沙箱裡產出的草稿,變成一個待人工終審 PR 的那一段機器。它原本只認兩種產出:內容草稿(神祇、文章的 md 檔)與圖像題。節慶建檔(calendar-feast)的產出型態不一樣——它不是新建一個 md 檔,而是往 festivals.yaml 追加一段 YAML 條目。橋接沒有它的處理路徑,於是沙箱 run 只能把結果寫成 .json.ready 積在原地等人工,當時已積了 2 筆,佇列後方還排著同型任務。

    補上的做法是新增一條 kind:'yaml-append' 分岔:從草稿裡抽出唯一一個 YAML 區塊,用 js-yaml 先驗「追加之後整份檔案仍可解析、且 name 沒有重複」,通過才追加到目標檔,接著開分支、commit、push、印出 PR 連結。有兩點刻意跟內容草稿流程不同——草稿保留不刪(裡面存著稽核表與內文修正建議),以及 merge 之後 post-merge 沒有這型別的自動收尾(沒有 frontmatter 可翻狀態),佇列任務得人工標 done。多個分支追加到同一個檔尾,merge 第二筆時可能要 rebase 解尾端衝突——這個限制照實記在契約裡。上線前在 scratch clone 端對端跑過兩筆真實記錄(--no-push),確認分支、驗證、佇列流轉都對,才把契約寫進 content-loop.md 的 Stage 5。

    同一天順手補了審核通知的體驗:待終審的 Telegram 通知原本只列出 slug 與分支名,終審的人得自己翻本機 log 或手動拼網址,才拿得到開 PR 的連結。抽出一個 prUrl() 統一組連結,三處通知文字與 console log 都改成直接附上,少一段不必要的查找。

    這兩筆都不是華麗的新功能,而是自治閉環的基礎建設補強。但橋接要接的縫,正是「AI 產出」與「人工終審」之間那一段——每少一次人工查找、每多接住一種產出型別,這條把發布權留在人手上、把重複勞動交給機器的流水線就更順一分。

  22. 功能 ADR 0008

    頁面開始記得誰來過——瀏覽數與「其他人也讀了什麼」

    廟宇、神祇、故事、專題與知識庫文章的頁尾,現在會累計這一頁被閱讀的次數;等足夠多讀者走過同一條路徑,還會浮現「其他人也讀了什麼」的同類推薦。全程匿名——不發 cookie、不存 IP、不追蹤任何人。剛上線時的安靜是預期中的醞釀:數字與推薦,會隨香火自己長出來。

    工程細節

    一個月前的規劃(spec v0.1)原本要把分析後端放到自有主機的 PHP+MySQL 上;這次覆閱時整個翻案(ADR 0008):自治營運落地後 launchd 運行時已就緒、「Pages 不能 cron」不再成立,更關鍵的是發現推薦根本不需要每日聚合——讀取當下對單一實體做索引點查即可。於是整套系統縮進站本體:獨立 D1 miao-analytics、同源 /api/track 與 /api/stats,零第二主機、零 CORS、零 cron,對一人營運是最小的維運面。

    攝取走旁路,渲染路徑一根手指都不碰。計數靠頁面載入後 idle 時機的 sendBeacon——fire-and-forget,後端慢了掛了使用者都無感,靜態頁的 cache 命中與 TTFB 原封不動。哪些頁要計數由 BaseLayout 的 analytics prop 點名:五類內容頁傳入即啟用,hub、列表與工具頁沒傳就是零輸出,不需要維護任何黑名單。

    推薦是即時算的。同一位訪客近期讀過的同類頁面,會以對稱共現對(字典序去重)累計進 covisit;讀取時兩條索引各掃一段、取 top-K,一次查詢就是一份推薦。共現次數不到門檻(MIN_CNT=3)不顯示——既擋雜訊,也避免「只有一個人走過的路徑」洩漏個人足跡。冷啟動期間整個區塊優雅隱藏,不出現空框。

    防刷與消毒是分層的。JS beacon 天然濾掉不跑 JS 的爬蟲;zone 層 Rate Limiting 掐住腳本洪流;端點驗來源白名單、丟 bot UA;前端 30 分鐘節流擋狂刷重整。標題與網址來自 beacon、屬攻擊者可控,入口 strip tags+僅收站內相對路徑,輸出端一律 textContent 組 DOM——雙重防護。定位誠實:這是熱度訊號,不是審計計票。

    維運掛進既有的骨架。分析庫併入每日 D1 匯出 → R2 備份輪替(庫未建也只警告、不擋部署);額度天花板誠實量化過——最緊的 D1 寫入約當日均一萬四千次瀏覽,離現況兩個數量級,超標的路是五美元月費,最後的退路是那台已確認可用的老 PHP 主機。願它永遠用不上。

  23. 功能

    首頁多了一扇「今日聖誕」曆牌——把神明行事曆迎到最顯眼的地方

    過去神明行事曆只藏在頁尾與內文連結裡,訪客不容易遇見。這次替首頁加了一扇黃曆曆牌浮窗:左頁報今天農曆幾號、什麼干支,右頁列出近日輪到哪幾位神明的聖誕。信眾一進站就知道「這幾天該向誰上香」,把日常時序和神明重新繫在一起。

    工程細節

    行事曆一直都在,但它只掛在頁尾與文章內連結——訪客得先想到要找、才找得到。神明與信眾的連結,本該由「時序」牽起:初一十五、某位神明的聖誕,是信仰在生活裡的節拍。把行事曆迎到首頁,等於把這條節拍還給每一位進站的人。

    黃曆曆牌浮窗(DeityFeastWidget)。定調成一張對開曆牌:左頁是今日農曆大字+干支,順帶答了「今天農曆幾號」這個最常被問、卻最難即時查的問題;右頁是近日聖誕清單,寫到誰、就能一鍵連到那位神明的百科與供奉廟宇。它是首頁一個顯眼但不擾人的入口,也是行事曆的縮影。

    淡季不留空窗。神明聖誕在曆上分佈不均,硬性「只看近五日」會在淡季開天窗、反而顯得冷清。upcomingFeasts 加了第 4 個參數 minCount=3:近五日不足三筆就無限往後追到補滿,標題隨之在「近五日聖誕/近期聖誕」之間切換,遠日的相對時間顯示為「N 天後」。無論哪一天進站,曆牌上都有神明在等你。

    行事曆改成 18 個月西元滾動視窗。原本要靠人工逐年延展資料;改成「當月一日起 18 個月」過濾、buildCalendar 取 [今年, +1, +2] 三年現算農曆,配合每日 04:30 的 deploy 重 build,日期層每天自動往前滾一格,再也不會有「明年的初一查不到」。

    農曆解析抽成 SSOT(feast-parse)。曆牌與行事曆缺口自治都要把「農曆某月某日」的聖誕字串解析成日期,原本 regex 散在兩處、容易漂移。抽出 parseFeastDay 為純 JS 模組(先切尾註再比對格式),Node 與 Vite 兩端共載一份,來源唯一。

    缺口自治(calendar-gap)。有些神明香火鼎盛、卻因聖誕字串格式不齊而無法在行事曆現身。新增 calendar-gap.mjs 掛進每週 scan.mjs,把「有香火卻上不了行事曆」的神祇撈進人工佇列補列,香火門檻(廟數)由 20 下修為 12,讓更多地方神也能被時序接住。

    當晚緊接著一輪體感潤飾(fix 94b0a2c):收合/關閉鈕改成等大 26px 方框+SVG 線條做幾何對齊;收合態把左曆牌縮成一行日期 chip、整張壓成約 78px 矮 bar,消掉右側留白;電腦版依回饋從底部置中改到右下角靠邊,手機版維持貼底滿版。一扇曆牌,要夠顯眼、也要夠安分。

  24. 功能

    社群分享圖上線——接進神像閉環

    把網站連結分享到社群平台,現在會亮出漂亮的預覽圖了。首頁與神祇頁先行,而且神像定稿時會自動連分享圖一起產好。

    工程細節

    Open Graph/Twitter 卡從首頁與神祇頁做起——神祇頁是最常被分享的頁型,且神像素材現成。

    重點不在圖而在管線:分享圖產製直接掛進 deity-image 閉環,神像定稿的同時自動產出該尊的 OG 圖,不另設人工步驟。新神像上頁即帶分享圖,存量再逐步回補。

    失敗路徑也補了通知:神像 PR 產分享圖失敗時發 TG,不再只寫 console——自動化管線裡沉默的失敗最貴,這條原則在整個自治體系裡一體適用。

  25. 營運

    神像產圖閉環 v0.2——免費半自動產線

    神像圖也有了產線:AI 產圖、Telegram 前置審核、定稿自動上頁。既有十尊神像同步更新為去浮水印版本,並新增雷震子、東海龍王、地藏王菩薩、玉皇上帝四尊。

    工程細節

    6 月手工做神像的痛(產圖、去浮水印、逐尊上頁)收斂成半自動閉環:產圖 → TG 發圖前置審 → 人在 TG 裡定奪 → 定稿自動進站。走免費產圖方案,v0.2 版本號誠實反映它還在演進(規格定案是 Gemini API 路線,現階段先用免費半自動頂著)。

    TG 通知按主題分流:產圖摘要發產圖 thread(TG_IMAGE_THREAD_ID),不跟內容閉環的通知混流。存量整理同步做掉:十尊舊神像換去浮水印版,再補四尊新神像。

    缺口統計:全神祇庫尚缺 94 張神像——這條產線的存在意義就是把這個數字磨到零。

  26. 營運 ADR 0007

    AI 自治營運落地——通知、備份、護欄、儀表板

    網站開始「自己照顧自己」:每日排程自動運作,所有動作發 Telegram 通知,資料每日備份輪替,進度儀表板公開透明。人只在關鍵處把關。

    工程細節

    ADR 0007 從規格變成現實的兩天。通知線:所有自動化動作發 TG(支援主題群組、格式定版),配速率護欄與 T3 觸發詞攔截——高風險動作直接擋下待人工。備份線:R2 備份輪替,近 30 天每日檔+月初檔留 12 個月。儀表板:公開 /stats/ 覆蓋率頁+Cloudflare Access 保護的 /ops/ 工程進度頁。

    護欄是重頭戲:token 預算 ledger(月度取題上限 60,逾限停題+TG 通知)、work-queue 檔案鎖防排程重疊互蓋(claim 原子化,還得繞過沙箱禁刪檔的 EPERM)、終審收尾自動化(post-merge 腳本接 deploy 流程)。

    實體機的雷也一併處理:launchd 排程以 caffeinate 包裹防執行中睡眠、04:25 wake-window 撐兩小時涵蓋部署與內容閉環。7/17 常態排程開跑,每日 05:30 自動產出。

  27. 營運

    神祇自動建檔閉環——AI 寫、AI 稽、人終審

    神祇建檔進入半自動時代:AI 起草、AI 稽核、開 PR、人工終審後自動收尾。普庵祖師、三府王爺、西方三聖是頭三尊走完全流程的神祇。

    工程細節

    內容閉環的完整路徑:排程取題(照主祀覆蓋率缺口排序)→ AI 草稿 → AI 稽核 → 自動分支開 PR → TG 通知 → 人工終審 merge → post-merge 自動收尾(reviewStatus→reviewed、補 lastVerified、回推部署)。

    頭三尊試金石:普庵祖師(7/16 首發)、三府王爺、西方三聖(7/17)。三篇皆以 auto/deity-* 分支走 PR 流程,7/17 終審收尾一次補上狀態欄。

    設計原則是「AI 可以做很多,但發布權在人」:PR 是強制關卡,reviewStatus 生命週期(draft→ai-audited→reviewed→published)讓每篇內容的查核狀態可稽核。取題量由 token 預算 ledger 節流,不因為自動化就無限產出。

  28. 品牌

    品牌正名「小島神話」與正式網域 myths.tw

    站名從「神島誌」正名為「小島神話」,正式網域 myths.tw 上線,並裝上 Google Analytics 開始看見讀者。

    工程細節

    「神島誌」是 6/11 定的站名,用了一個月後正名「小島神話」——更口語、更接近內容本體(島嶼的神話),英文域名 myths.tw 也能對上。改名是全站 refactor:站名散落首頁、頁尾、meta、通知前綴,一次收攏。

    網域架構:myths.tw zone 同帳號代管,root CNAME 指向 Pages(proxied);pages.dev 預設網域保留可存取,全站加 self-canonical 指向正式網域,SEO 權重不分裂。

    GA4 同日全站安裝——結果隔天才發現追蹤碼從未觸發:inline script 被誤用 JSX 模板包裹,Astro 把它當成需要 hydration 的內容處理掉了。7/17 修正,教訓是「裝了」和「動了」是兩件事,第三方腳本上線要看得到資料才算數。

  29. 功能

    手機版跑版總清理與 Playwright 掃描

    修掉手機版四處中文逐字直排的跑版與導覽列破版,並建立自動掃描腳本——之後每次改版都能一鍵檢查 22 個頁面模板有沒有跑版。

    工程細節

    中文跑版的病灶很典型:flex/grid 擠壓下中文逐字直排。一次掃出四處修掉,並把「病灶模式」寫成自動檢測:Playwright headless 掃頁面橫向溢出、逐字直排、元素超出視窗三種症狀,命中即 exit 1。

    改用 Playwright 也是工作流決策:瀏覽器擴充對 .test 本機網域無法記住授權,每個動作都跳權限視窗。腳本直打 dev server(繞過 https 代理),無參數掃 22 個代表頁模板,--shot 存全頁截圖,--viewport 換尺寸。

    順帶記一條當天踩的雷:npm install 改寫 package.json 觸發 dev server 設定重載,可能讀到寫一半的 JSON 卡成半殘(程序活著但 port 沒聽)——裝完套件要確認 port,沒聽就 kickstart。

  30. 視覺

    神像上頁與流光特效

    神祇頁開始有「臉」了:關聖帝君、觀世音菩薩、七星娘娘、二郎神、中壇元帥的神像陸續上頁,搭配四種模式隨機的流光特效,列表卡片也加上神像背景。

    工程細節

    點雲撤回後的新路線:AI 產製平面神像圖+CSS/JS 流光特效。流光做了四種模式隨機(含香火),每次進頁氛圍略異;特效中途改組過一次架構以便掛更多神像。

    一個有意識的決策:移除了 prefers-reduced-motion 關閉特效的機制。特效是神像呈現的一部分而非裝飾,且強度溫和;這個取捨記錄在案,若有使用者反映再檢討。

    此時神像仍是手動一尊一尊做——產圖、去浮水印、上頁。這個流程的痛感直接催生了 7 月的神像產圖半自動閉環(見〈神像產圖閉環〉)。

  31. 內容

    《神島演義》85 章上線——神祇庫擴至 101 尊

    站的第四根柱子:一部從開天闢地寫到眾神渡海來台的故事主線,序卷到終卷共 85 章。為了寫這部故事,神祇庫一口氣擴充到 101 尊。

    工程細節

    神祇百科是查閱層,《神島演義》是閱讀層——把整個神譜用一條世界觀時序串起來:開天、洪荒、天庭、封神、佛陀、西遊、地府、成神、人間九個敘事弧。每章標注 canonLevel(典據/演義/敷演),讀者分得清哪些有經典依據、哪些是民間演義、哪些是本站敷演。

    saga collection 沿用字串 fallback 策略連結神祇與廟宇,章內 featuredDeities 反向帶動神祇庫擴充:故事寫到誰、誰就建檔,單日從 75 尊擴到 101 尊。

    同日導覽列改版(搜尋改圖示、行事曆移頁尾),並新增了 /devlog/ 佔位頁——就是現在這個頁面,佔位將近一個月後終於回填。

  32. 營運 ADR 0006ADR 0007

    規格醞釀日——籤詩、自治營運、互動分析

    沒有新功能上線的一天,卻是後來一個月方向的源頭:六十甲子籤詩、AI 自治營運、互動分析三份規格同時開工。

    工程細節

    三條線同日立規格:六十甲子籤詩(ADR 0006 定編號穩定契約——籤詩編號一旦發布永不變動,外部連結才敢引用);AI 自治營運(ADR 0007 自治模型——哪些事 AI 可以自己做、哪些要人審,後來 7 月整套落地);互動分析(瀏覽數與「其他人也讀了什麼」,隔日補 XSS 防護設計與 18 個 task 的實作計畫)。

    配套的治理文件也在這兩天成形:內容版本紀錄與回溯 spec、維運登記簿(MAINTENANCE)。

    事後看,這是全案從「衝功能」轉向「建體制」的轉折點:之後的大事幾乎都是這幾份規格的執行。規格先行雖然當下沒有可見產出,但 7/16–7/17 自治營運能兩天全面落地,靠的就是這時打的底。

  33. 視覺 ADR 0003

    撤回立體點雲聖像——一次誠實的退場

    6/11 上線的 3D 點雲神像功能(關帝、媽祖首發)正式移除。做了、上線了、然後承認它不夠好——撤回也是開發的一部分。

    工程細節

    點雲聖像的構想:神祇個頁放一座由粒子構成的立體神像,呼應「神像是香火凝成的」意象。技術上做到了(three.js 點雲,關帝、媽祖兩尊首發),但存在九天後決定撤回、ADR 0003 標記撤回。

    撤回理由:視覺辨識度不足(點雲看不出是誰)、載入成本高(每尊都要點雲資料)、與後來 AI 產製神像圖的路線重疊——平面神像圖+流光特效(見〈神像與流光特效〉)在辨識度和擴充成本上全面勝出。

    留下 ADR 而不是默默刪碼,是刻意的:決策紀錄要包含「做錯的決策」,否則下次還會在同一個地方心動。

  34. 資料工程 ADR 0004

    奉祀四分法——主祀、配祀、從祀、同祀

    廟裡拜的不只一尊神。資料模型升級為主祀/配祀/從祀/同祀四分法,廟宇頁與神祇頁能完整呈現一間廟奉祀的神明結構。

    工程細節

    原本 temples 只有 mainDeity 一欄,塞不下真實廟宇的奉祀結構(霞海城隍廟的月老怎麼放?)。ADR 0004 定案四分法:主祀維持獨立欄位,其餘以 enshrines 陣列帶角色(配祀/從祀/同祀)+粗略殿位。

    雙軌落地:策展廟(md)走 schema 的 enshrineSchema;全量廟(D1)新增 temple_deities junction table。種子案例台北霞海城隍廟,第一版顯示直接吐 slug,隨即修成聖號。

    神祇參照沿用「優先 slug、未建檔填原名字串、純文字 fallback」的過渡策略——不用 reference() 硬約束,內容擴充不被 schema 卡死。同日順手開了 ADR 0005 廟宇空間配置的坑(見次篇)。

  35. 資料工程 ADR 0005

    廟宇空間配置模型——把一間廟畫進資料

    開始把廟宇的空間格局(哪個殿、哪個龕、拜哪尊)建成結構化資料,以艋舺龍山寺做第一個試點。

    工程細節

    ADR 0004 的 hall 只是粗略殿位文字,ADR 0005 把野心放大:殿—龕—神的立體配置模型,未來能渲染成廟宇平面導覽。神龕的 role enum 與四分法共用,兩個模型咬合。

    試點選艋舺龍山寺——殿宇結構複雜度夠(前殿、正殿、後殿多龕)、資料公開充分。YAML 試點檔完成,驗證了模型裝得下真實廟宇。

    刻意只做到「規格+ADR+試點資料」就停:渲染層(Zod collection 化+P1 平面圖)另排期程,不跟四分法搶同一個檔期。大模型分段落地,每段都有可驗收的產出。

  36. 內容

    神祇覆蓋率衝刺與事實查核機制

    神祇百科單日新增 26 尊,全台主祀神明的建檔覆蓋率從 47% 衝到 75%。同時建立事實查核追蹤表,發現錯誤就修——當天就修掉了五年千歲科期等錯誤。

    工程細節

    覆蓋率是從全量廟宇資料反推的:D1 裡全台主祀神明統計出來後,「哪些神最多廟拜、但百科還沒建檔」一目瞭然,衝刺清單就是照這個排的。兩批共 26 檔(47%→70%→75%)。

    量衝上去之後馬上補質的機制:26 檔事實查核追蹤表,逐檔覆閱。當天抓到並修正的包括五年千歲科期與孚佑帝君封號,覆閱定案後再修開台聖王、濟公、五福大帝、瑤池金母四檔。「AI 草稿 → 事實查核 → 發布」的產製流程在這次衝刺中第一次真正跑完整輪,後來演化成 reviewStatus 生命週期欄位。

  37. 資料工程

    全量廟宇 P1b——一萬兩千頁與一張地圖

    全台一萬兩千多間登記寺廟每間都有自己的頁面了,還能在互動地圖上找廟。缺座標的廟宇也補上了定位。

    工程細節

    12,414 筆個頁不可能全部 SSG(build 時間與產物體積都爆),定案混合渲染:/temples/t/[id]/ 走 SSR+Cloudflare D1,其餘維持靜態。SQLite 同步到 D1 的管線同日打通,本地 dev 用 miniflare 模擬 D1(.wrangler/state/),ETL 重跑後要 sync:d1:local 重灌,忘了就是本地 500——這條雷之後踩過不只一次,最後寫進 CLAUDE.md。

    /temples/all/ 提供全量名錄的排序與搜尋(D1 LIKE,之後和 Pagefind 靜態搜尋分工)。缺座標廟宇跑 geocoding 補值,然後互動地圖上線;地圖後來(同日稍晚)併入廟宇頁作為檢視模式,點廟彈基本資料卡,配色調成站內暖色深色風。

    SSR 路由的 sitemap 是額外功課:sitemap-temples.xml.ts 於 build 自產,這也定下了「新增 SSR 路由要手動納 sitemap」的守則。

  38. 視覺

    首頁 Hero 重建——捲動香煙

    首頁門面砍掉重練:拿掉創站日的星圖節點與燈籠,換成一縷隨捲動流轉的香煙。

    工程細節

    創站日的星圖 hero 問題不斷:節點標籤和副標重疊修了一次、窄視窗又和主標題重疊再修一次,視覺密度也太高。與其繼續補,不如承認方向錯了。

    重建走 D 版方案「捲動香煙」:單一意象、隨捲動有機流轉,把「進廟先聞到香」的體感放進首頁。移除節點圖與燈籠後,首屏資訊密度大降,站名與標語反而立起來。

    留下的教訓:hero 這種高風險視覺,先在草稿頁比稿再上正頁比較省——這也是後來 /labs/ 沙盒頁誕生的原因之一(搜尋頁版型就是先在 labs 打稿)。

  39. 功能

    基礎建設一日四發——搜尋、SEO、行事曆、知識庫

    一天之內補齊四樣基礎配備:全站搜尋、搜尋引擎與 AI 友善設定、宗教節慶行事曆(含農曆換算)、知識庫分類入口。

    工程細節

    搜尋選 Pagefind:build 後索引靜態 HTML,零 runtime 後端,中文 CJK 分段可用。已知限制誠實記錄在規格:中文複合詞查詢端與索引端斷詞不一致(「媽祖」查不到「媽祖遶境」連寫的文章),v1 接受,導流 D1 名錄搜尋補位。搜尋頁版型先在 /labs/ 沙盒打草稿再定稿,資料層用 Pagefind JS API 自繪 DOM 而非官方 UI。

    SEO/AI-friendly 全套:結構化資料、RSS、llms.txt——把「AI 引用友善」當成一級公民,這站本來就打算讓 AI 讀得懂。行事曆做了農曆換算,宗教節慶以農曆為準,這是繞不開的功課。知識庫 hub 支援 wikilink 解析,讓知識條目之間互相引用不用寫完整路徑。

  40. 功能

    創站日——從零到視覺骨架

    網站誕生的第一天。用 Astro 搭起整個站的骨架,定下暖色深色的視覺基調——星空、燈火、廟埕的氛圍,從第一天就是現在這個樣子。

    工程細節

    技術選型 Astro v5:內容型網站、SSG 為主,動態需求(後來的全量廟宇個頁)再用混合渲染補。先寫規格再動工的守則也是這天定的——里程碑一「視覺骨架」規格定案後才開 scaffold。

    Design tokens 起手:色彩、字距、間距全部進 CSS variables,之後所有頁面共用同一套 --c-* 變數,改版基調只動一處。首頁 hero 首版是「星圖節點+燈火動態」——這版後來在 6/12 被整個重做(見〈首頁 Hero 重建〉),但星空的意象留了下來。

    同日踩雷:本機 dev 網域 miao.test 的 https 代理設定折騰了兩輪,最後定案 Herd secure proxy;dev server 隔天進一步改由 launchd 常駐,crash 自動重拉。

  41. 功能

    神明位階圖——從 2D 到 3D

    把神明之間的位階與統轄關係畫成一張可以互動的圖:2D 版人人可用,桌機預設 3D 版可以旋轉俯瞰整個神譜。

    工程細節

    資料先行:rank-tiers 位階序列+perspectives 逐視角出處,先把「誰管誰、依據是什麼」建成資料,圖只是渲染。位階圖資料 JSON 於 build 時預生成,前端不用重算。

    一天內從 2D 疊到 3D(three.js),桌機預設 3D、行動裝置退回 2D。神祇個頁另做 ego 特寫——以該神為中心的局部位階圖。後續踩雷兩則:3D 場景在背景分頁切回來時鏡頭會貼臉(6/12 修,順手加了 Z 軸統轄縱深);dev 模式下圖學依賴載入會 504(6/21 改預打包解掉)。

    入口位置也調整過:一開始掛主選單,6/12 移到神祇頁入口卡——位階圖是神祇百科的延伸,不是獨立頻道。

  42. 資料工程 ADR 0002

    全量廟宇名錄 P1a——政府資料進站

    不只介紹精選廟宇——把政府登記的全台上萬間寺廟資料接進站內,可以依 22 縣市瀏覽,神祇頁也長出全台主祀統計。

    工程細節

    ADR 0002 定調資料層雙軌制:策展內容永遠是 Markdown(人寫的、要查核的),機器量級資料(政府寺廟登記逾萬筆)走 ETL 管線——CSV 快照+YAML 修正層 → SQLite。修正層是關鍵設計:政府資料有錯不改快照本體,錯誤修正寫進 YAML 疊加,快照可隨時換新版重跑。

    P1a 範圍守在靜態可承受的部分:22 縣市 hub 頁(SSG)、策展廟宇與登記資料的對應、神祇頁全台主祀統計。上萬筆的個頁留給 P1b,因為那需要 SSR。

    這是全站第一次「策展層」與「資料層」正面整合,雙軌各自演進、接點明確的架構從這裡定下。

  43. 內容

    三大內容主體首發——廟宇、文章、神祇百科

    站的三根柱子同日立起:策展廟宇介紹、三層深度的文章、以及神祇百科。神祇從首批 6 尊當天擴到 22 尊,補齊地府統轄鏈。

    工程細節

    Content collections 是內容層的地基:temples、articles、deities 三個 collection 各配 Zod schema,Markdown 為單一事實來源(SSOT),仿 taiwan.md 的產製架構。

    神祇百科的設計重點在「關係」:另立 relations 資料層(統轄、師徒、化身、配偶、對立⋯⋯),神祇個頁直接長出神譜關係區塊。首批就刻意收了「矛盾並存」案例——同一尊神在不同體系有不同位階,不硬扣單一真相,逐視角列出處。這個原則成了之後整個神譜資料模型的基調。

    文章走三層深度(30 秒/5 分鐘/深讀),對應不同讀者的耐心預算。當天內容量:22 尊神祇、含地府統轄鏈(城隍—東嶽—十殿閻羅)。

  44. 內容

    地府專題上線

    第一個主題式專題:地府。從城隍、東嶽大帝到十殿閻羅,五篇文章加三間廟宇,把民間信仰裡的冥界行政體系整理成一條完整的閱讀線。

    工程細節

    Phase B 的驗證題目:三大內容主體(廟宇、文章、神祇)能不能被一條敘事線串起來。選地府是因為它結構最清楚——統轄鏈明確(城隍隸東嶽、十殿閻羅分工),跨廟宇(城隍廟、東嶽殿)、跨神祇、跨文章都有得寫。

    實作是內容工程多於程式:城隍與東嶽體系廟宇三間、地府內容線五篇、一個專題 hub 頁把線收攏。專題 hub 這個版型後來成為「主題策展」的模板。

    Phase B 完工時神祇規格驗收六條全過,證明了 collection schema+關係資料層撐得起主題策展,才敢往下接全量廟宇名錄這個更大的坑。