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

開發日誌

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

  1. 功能

    文章網址搬家——/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 自癒。

  2. 品牌

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

    小島神話終於有了自己的圖案。設計系統裡定稿的標記「雙嶼日出」——朱砂的雙島、金色的日與海線——正式放進網站左上角的站名旁邊,也做成了 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 時最不容易誤傷的形狀。

  3. 營運 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-tgimage-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 位址連入不在白名單內)也一併記了。

  4. 內容 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」的舊條目用劃線保留沿革、下面補記現況——規格改動留下歷史痕跡,比直接覆寫更能解釋「為什麼會變成現在這樣」。

  5. 營運 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 產出」與「人工終審」之間那一段——每少一次人工查找、每多接住一種產出型別,這條把發布權留在人手上、把重複勞動交給機器的流水線就更順一分。

  6. 功能 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 通知人工看。這是刻意的順序:先讓話有地方講,再想怎麼把話整理好。

  7. 功能 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 主機。願它永遠用不上。

  8. 功能

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

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

    工程細節

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

    黃曆曆牌浮窗(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,消掉右側留白;電腦版依回饋從底部置中改到右下角靠邊,手機版維持貼底滿版。一扇曆牌,要夠顯眼、也要夠安分。

  9. 功能

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

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

    工程細節

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

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

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

  10. 營運

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

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

    工程細節

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

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

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

  11. 功能

    手機版跑版總清理與 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。

  12. 營運 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 自動產出。

  13. 營運

    神祇自動建檔閉環——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 節流,不因為自動化就無限產出。

  14. 品牌

    品牌正名「小島神話」與正式網域 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 修正,教訓是「裝了」和「動了」是兩件事,第三方腳本上線要看得到資料才算數。

  15. 視覺

    神像上頁與流光特效

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

    工程細節

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

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

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

  16. 內容

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

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

    工程細節

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

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

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

  17. 營運 ADR 0006ADR 0007

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

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

    工程細節

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

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

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

  18. 視覺 ADR 0003

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

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

    工程細節

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

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

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

  19. 資料工程 ADR 0004

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

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

    工程細節

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

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

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

  20. 資料工程 ADR 0005

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

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

    工程細節

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

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

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

  21. 內容

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

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

    工程細節

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

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

  22. 資料工程

    全量廟宇 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」的守則。

  23. 視覺

    首頁 Hero 重建——捲動香煙

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

    工程細節

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

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

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

  24. 功能

    基礎建設一日四發——搜尋、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 解析,讓知識條目之間互相引用不用寫完整路徑。

  25. 資料工程 ADR 0002

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

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

    工程細節

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

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

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

  26. 功能

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

    網站誕生的第一天。用 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 自動重拉。

  27. 功能

    神明位階圖——從 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 移到神祇頁入口卡——位階圖是神祇百科的延伸,不是獨立頻道。

  28. 內容

    地府專題上線

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

    工程細節

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

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

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

  29. 內容

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

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

    工程細節

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

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

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