mosaic-headless 1.17.0 → 1.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.zh-TW.md CHANGED
@@ -3,110 +3,253 @@
3
3
  [![npm downloads](https://img.shields.io/npm/dt/mosaic-headless?label=npm%20downloads&color=cb3837)](https://www.npmjs.com/package/mosaic-headless)
4
4
 
5
5
  直接寫入資料模型來建置與修改 [Mosaic Pro](https://mosaicbuilder.com)(Nextend)網站——
6
- 不開視覺編輯器,不碰 DOM。
6
+ 不開視覺編輯器,不碰 DOM。把 Elementor 頁面轉進來。把整個主題搬到另一個站。
7
+ 每一項宣稱都在真實站台上量過。
7
8
 
8
9
  *其他語言:[English](README.md) · [日本語](README.ja.md) · [한국어](README.ko.md)*
9
10
 
10
11
  ---
11
12
 
13
+ ## 安裝
14
+
15
+ ```bash
16
+ npx mosaic-headless # 互動式:選平台
17
+ npx mosaic-headless claude-code --global # Claude Code,裝到 ~/.claude/skills/
18
+ npx mosaic-headless cursor --to ./my-project
19
+ npx mosaic-headless --list # 全部八個平台
20
+ ```
21
+
22
+ | 平台 | 裝什麼 | 裝到哪 |
23
+ |---|---|---|
24
+ | Claude Code | 完整技能:SKILL.md + references/ + tools/ + data/ + sites/ | `~/.claude/skills/` 或 `./.claude/skills/` |
25
+ | Codex CLI | 完整技能 | `~/.codex/` |
26
+ | Gemini CLI | 完整技能 | `~/.gemini/` |
27
+ | GitHub Copilot | 完整技能,外加一段附到 `copilot-instructions.md` | `./.github/` |
28
+ | Cursor | 一個內嵌 references 的 `.mdc` 規則檔 | `~/.cursor/rules/` |
29
+ | Windsurf | 一個內嵌 references 的規則檔 | `./.devin/` |
30
+ | Continue | 一個內嵌 references 的規則檔 | `~/.continue/` |
31
+ | Claude.ai | 一個 zip,上傳為專案技能 | 你存的地方 |
32
+
33
+ 每個平台的安裝都由發布閘門對照其模板驗證。工具要跑需要 Python 3 和 Playwright;規則檔類的
34
+ 平台拿到的是知識,沒有工具。
35
+
36
+ **更新不會自己發生。** npm 上有新版,不代表你的 agent 載入的那個資料夾有變;要重跑安裝器並加
37
+ `--force`(不加的話它會拒絕覆蓋你可能改過的 SKILL.md):
38
+
39
+ ```bash
40
+ npx mosaic-headless@latest claude-code --global --force
41
+ ```
42
+
43
+
44
+ ## 這是什麼
45
+
12
46
  Mosaic 把一個頁面放在 **23 張自訂資料表**裡,不在 `post_content`,也不在 `postmeta`。
13
47
  一個元素一列,樹狀結構靠 `parentID` 欄位,同層順序是一個 fractional-index 字串。
14
48
  編輯器只是這個模型的其中一個客戶端。它不是格式本身,而且你不需要它。
15
49
 
16
- 這個 skill 就是那個模型的地圖——**對著真實安裝量出來的,不是從原始碼讀出來的**。
50
+ 這個技能就是那個模型的地圖——對照真實站台量出來的,不是讀原始碼讀出來的——外加一組
51
+ 工具:透過模型寫入、檢查寫出來的東西、把 Elementor 的頁面搬進來。
52
+
53
+ ## 各部分怎麼接起來
54
+
55
+ ```mermaid
56
+ flowchart LR
57
+ subgraph measure["量一次,對著真實站台"]
58
+ SRC[外掛原始碼] -->|extract_*.py| D[(data/*.csv)]
59
+ SW[sweep_*.py / probe_*.py] -->|寫入、渲染、斷言| D
60
+ end
61
+
62
+ subgraph write["你建的每一頁"]
63
+ Q[mo.py] -->|一個答案,量測結論在前| SPEC[頁面 spec]
64
+ EL[Elementor _elementor_data] -->|from_elementor.py| SPEC
65
+ SPEC -->|build_page.py 拒絕量到會壞的| REST[Mosaic REST:checkout、check、commit]
66
+ REST --> DB[(23 張表)]
67
+ DB --> PAGE[送出的頁面]
68
+ end
69
+
70
+ subgraph verify["永遠不信任 commit"]
71
+ PAGE --> V1[verify_rwd.py]
72
+ PAGE --> V2[verify_browser.py + 設計稽核]
73
+ PAGE --> V3[verify_intro.py / verify_loop.py]
74
+ PAGE --> V4[verify_conversion.py]
75
+ V1 & V2 & V3 & V4 --> CSV[(驗證表)]
76
+ CSV --> GATE[check-release.mjs]
77
+ end
78
+
79
+ D --> Q
80
+ D --> SPEC
81
+ ```
82
+
83
+ 由左到右:表格量一次、隨套件出貨;每一頁都透過表格寫入、表格說不行就拒絕;在送出的頁面被
84
+ 讀回來、結果記進發布閘門會檢查的表之前,什麼都不信。
17
85
 
18
- ## 唯一一條凌駕一切的規則
86
+ ## 唯一的規則
19
87
 
20
- **絕不憑印象寫節點型別、屬性名稱、列舉值、樣式鍵或 Free/Pro 的判斷。到 `data/` 裡查。**
88
+ **絕對不要憑記憶寫任何節點型別、屬性名稱、列舉值、樣式鍵或 Free/Pro 的判斷。去 `data/` 查。**
21
89
 
22
- 而且要用 `mo.py` 查,不要用 grep。grep 回答你打出來的問題,不回答你真正的問題:
23
- 問它 `accordion-content`,它確認這個型別存在;掃描表則說 BROKE_PAGE——放一個下去,
24
- 整個公開頁面會變成一段 54 bytes 的錯誤字串。兩個都是真的,也都是錯的答案:列旁邊的
25
- 註記說那段字串指名的是「缺少父層」,而照 `accordion > accordion-item` 嵌套後它能寫入、
26
- 能渲染,還免費送你一個鍵盤可操作的展開元件。`mo.py type` 一次把三者都給你。
90
+ 而且要用 `mo.py` 查,不要用 grep。grep 回答你打出來的問題,不回答你真正的問題。問它
91
+ `accordion-content`,它確認這個型別存在;掃描表則說 BROKE_PAGE——放一個下去,整個公開頁面
92
+ 會變成一段 54 bytes 的錯誤字串。兩個都是真的,也都是錯的答案:列旁邊的註記說那段字串
93
+ 指名的是「缺少父層」,而照 `accordion > accordion-item` 嵌套後它能寫入、能渲染,還免費送你
94
+ 一個鍵盤可操作的展開元件。`mo.py type` 一次把三者都給你。
27
95
 
28
96
  ```bash
29
97
  python tools/mo.py type accordion-content # 一個型別,接上所有線上掃描結果
30
- python tools/mo.py check div text button # 型別不安全或不存在就 exit 1
31
- python tools/mo.py style --grouped # 那 20 個單獨設定必然無效的屬性
32
- python tools/mo.py states --verified # 實測會編譯出來的狀態
98
+ python tools/mo.py check div text button # 遇到不安全或不存在的型別就以 1 退出
33
99
  python tools/mo.py params text # 一個型別上所有能設的東西
100
+ python tools/mo.py style --grouped # 單獨設定就無效的那 20 個
101
+ python tools/mo.py states --verified # 實測會編譯的狀態
102
+ python tools/mo.py css grid-column # 哪個 Mosaic 鍵驅動這條 CSS
34
103
  ```
35
104
 
36
- 然後去看頁面。Mosaic 有四種失效模式,**其中只有一種會改變 HTTP 狀態碼**:
105
+ 然後去看頁面。Mosaic 有**七種**失敗模式,只有兩種會改變 HTTP 狀態碼:
37
106
 
38
107
  ```
39
- 驗證器乾淨地拒絕 HTTP 200 + body 裡一個 exceptions 陣列
40
- commit 時 PHP fatal HTTP 500 (122 種型別裡有 15 種,光放在 div 裡就會)
41
- 結構無效的節點 HTTP 200、已寫入、資料庫有那一列,然後整個公開頁面
42
- 變成一段 54 bytes 的錯誤字串
43
- 值的「形狀」錯誤 HTTP 200、已儲存,而那條 CSS 規則就是不存在
44
- 規則對,結果不對 HTTP 200、在樣式表裡、內容正確,而瀏覽器算出來是另一回事
45
- 該網址沒有對應範本 HTTP 406,而且對未登入者是空白 body
108
+ 驗證器乾淨拒絕 HTTP 200 + 內文帶 `exceptions` 陣列
109
+ commit 時 PHP fatal HTTP 500(122 個型別裡有 15 個放在一般 div 下會這樣)
110
+ 結構無效的節點 HTTP 200、已寫入、資料庫有那一列,然後整個公開頁面
111
+ 變成 54 bytes 的錯誤字串
112
+ 值的「形狀」錯了 HTTP 200、存進去了,CSS 規則就是不出現
113
+ 規則對、結果錯 HTTP 200、樣式表裡有、寫得對,瀏覽器算出另一個值
114
+ 該網址沒有模板 HTTP 406,未登入者拿到空白內文
115
+ 內容造成渲染時 fatal HTTP 500——commit 通過了,Mosaic 解析頁面時死掉。`code` 節點
116
+ 的內容是模板:壓縮 CSS 慣用的 `@media(` 會被讀成函式呼叫。
117
+ `@media (` 就正常。build_page 會拒絕前者。
46
118
  ```
47
119
 
48
- **commit 成功不能當作頁面正常的證據,樣式表正確也不能。**
120
+ commit 成功不代表頁面能用,樣式表正確也不代表。這裡每個工具都會在寫入後把頁面抓回來——
121
+ 而且把 5xx 當成空頁面,因為 WordPress 的「嚴重錯誤」畫面有 2,697 bytes,比任何天真的
122
+ 「健康頁面」門檻都大。
49
123
 
50
124
  ## 驗證了什麼,怎麼驗的
51
125
 
52
- 全部跑在真實安裝上——WordPress 7.1、WooCommerce 11.1、Mosaic Pro 1.0.7、**未授權**:
53
- 授權管的是主題庫與更新,不是節點工廠,所以 Pro 型別照樣註冊、照樣渲染。
126
+ 全部在真實站台上跑——WordPress 7.1、WooCommerce 11.1、Mosaic Pro 1.0.7,**未授權**:
127
+ 授權鎖的是主題庫和更新,不是節點工廠,所以 Pro 型別照樣註冊、照樣渲染。
54
128
 
55
129
  | 項目 | 結果 |
56
130
  |---|---|
57
- | **節點型別** | 122 / 122,一份文件一種型別:寫入 → 渲染 → 斷言 → 刪除。70 RENDERED、30 COMMITTED、15 COMMIT_5xx、7 BROKE_PAGE |
58
- | **樣式屬性** | 98 / 98 寫進真實頁面並對照編譯後的 CSS:58 COMPILED、18 ABSENT、21 SKIPPED |
59
- | **節點屬性** | 181 / 181,用每個屬性自己的 validator chain 推導出的值重測:35 APPLIED、42 NO_EFFECT、55 NO_HOST、47 SKIPPED |
131
+ | **節點型別** | 122 / 122 逐一放進獨立文件,寫入 → 渲染 → 斷言 → 刪除:70 RENDERED、30 COMMITTED、15 COMMIT_5xx、7 BROKE_PAGE。未渲染的當中有三個是「沒給父層」的掃描方法產物,註記就在列旁 |
132
+ | **樣式屬性** | 98 / 98 寫進真實頁面、對照編譯出的 CSS:58 COMPILED、18 ABSENT、21 SKIPPED |
133
+ | **節點屬性** | 181 / 181 用各自驗證鏈推出的值重新探測:35 APPLIED、42 NO_EFFECT、55 NO_HOST、47 SKIPPED |
60
134
  | **響應式** | 兩個站共 731 條 `_t`/`_m` 宣告,對照網站實際送出的樣式表逐條斷言——全數通過 |
61
- | **元件系統** | 完整驅動過一遍,**8 / 8**:在分類下建立、文件 heal、透過可寫的 instance 填入內容、唯讀的那個作為負對照組確實拒絕同一個寫入,最後兩個實例在頁面上由同一份定義渲染兩次 |
62
- | **樣式狀態** | 53 個狀態中的 52 個寫進真實頁面,對照表格承諾的選擇器逐一比對:**36 個完全吻合**、12 個 NO_HOST、3 個 SKIPPED、1 個 BROKE_PAGE。七個可用於任何元素的狀態全數驗證 |
63
- | **互動動畫** | 帶負對照組並讀回儲存列來探測 JS 動畫路徑:`propertyMetas` **確實**會被接受並儲存;屬性值仍然無法綁定,但界線現在很精確 |
64
- | **入口動畫** | 以單調時鐘在載入後十五個時間點取樣、做八項斷言——它有播、被動畫的 `@property` 計數器跑到 100、遮罩退出點擊判定、視窗內沒有任何內容卡在 opacity 0、真實點擊落在文件上、`prefers-reduced-motion` 下遮罩根本不存在、而一切靜止後仍有東西在動。相較於完全沒有動畫的同一頁只多掉一幀,因為它會等文件第一次排版做完才開始 |
65
- | **永續動畫** | 右下角一塊不斷自我印刷、點了會放大的版子,**28 項檢查**:暫停時間軸逐格比對證明週期性、在五個寬度、二十五個捲動停點量文字*與*控制項的遮擋、以「永遠讀不到」為失敗條件、用指標和 Enter 都能打開、reduced motion 下靜止。建在 Mosaic 自己的 accordion 上 |
66
- | **accordion** | `accordion-item` 與 `accordion-content` 在掃描表裡是 BROKE_PAGE;照工廠要求的方式嵌套後能寫入、能渲染,**7 之 7**。註記現在就在那一列旁邊 |
67
135
  | **瀏覽器** | 在 Chromium 三個視窗寬度上對兩個交付頁面做 3,988 次計算樣式讀取:2,929 條比對相符、912 條標為無法比對、**0 條被覆蓋** |
68
136
  | **設計稽核** | 對比度、字體回退、CJK 字距、水平溢出、文字裁切、每行字數——在瀏覽器裡跑,**26 項發現,每一項都有書面裁定**——沒寫理由的 acknowledge 會被發布閘門拒絕 |
69
- | **主題匯出/匯入** | 兩條路徑,都來回驗證過。`theme_export.php` 用 WP-CLI 把資料列搬成 JSON、ID 原封不動,副本送出的頁面逐位元組相同。`theme_zip.py` 走 Mosaic **自己**的 milestone 協定驅動 ZIP 匯出匯入——匯入預設進 test mode,要 `--activate` 才上線,因為它的預設是直接把 live 站切過去——並以 **22 項檢查**逐表、逐樹比對副本與來源:每張表相等、68,337 個節點 ID 保留、override 節點重新發 ID,唯一少的一列是走樹本來就不該帶的孤兒 |
137
+ | **元件** | 元件系統從頭驅動到尾,**8 之 8**:在分類下建立、文件自癒、透過可寫實例填入樹、唯讀實例拒絕同一筆寫入作為負控制、頁面上兩個實例渲染同一個定義 |
138
+ | **樣式狀態** | 53 個狀態中的 52 個寫進真實頁面,對照表格承諾的選擇器:**36 個完全吻合**、12 NO_HOST、3 SKIPPED、1 BROKE_PAGE。偽類是大寫輸出的(`.M_EL9:HOVER`) |
139
+ | **互動** | JS 動畫路徑以負控制探測並讀回資料列:`propertyMetas` **會**被接受並儲存;屬性值仍然綁不上,但邊界現在是精確的 |
140
+ | **accordion** | `accordion-item` 與 `accordion-content` 在掃描表裡是 BROKE_PAGE;照工廠要求嵌套後能寫入、能渲染,**7 之 7** |
141
+ | **入口動畫** | 以單調時鐘在十五個時間點取樣、做八項斷言。相較於完全沒有動畫的同一頁只多掉一幀,因為它會等文件第一次排版做完才開始 |
142
+ | **永續動畫** | 右下角一塊不斷自我印刷、點了會放大的版子,**28 項檢查**:暫停時間軸逐格比對證明週期性、五個寬度、二十五個捲動停點量文字*與*控制項的遮擋、以「永遠讀不到」為失敗條件、指標和 Enter 都能打開、reduced motion 下靜止 |
143
+ | **Elementor 轉換** | 一個正式站的全部 Elementor 頁面——19 頁、3,292 個元素——轉換、建置、對照來源檢查:**19 之 19**,3,281 個元素搬過去、11 個書面宣告。再把轉出的頁面跑過響應式、瀏覽器和稽核,每一項發現都分類為「繼承」或「引入」:**引入 0 個** |
144
+ | **主題匯出/匯入** | 兩條路徑,都來回驗證過。`theme_export.php` 用 WP-CLI 把資料列搬成 JSON、ID 不變。`theme_zip.py` 驅動 Mosaic **自己**的 ZIP 匯出匯入——匯入預設進 test mode,要 `--activate` 才上線,因為它的預設是直接切換 live 站——**22 項檢查**逐表、逐樹比對副本與來源 |
145
+ | **技能本身** | `claude plugin eval .`——五個使用者真的會問的問題,各跑三次,有載技能和沒載各一臂,每次三個 LLM 裁判。**有:五題全 1.00。沒有:五題全 0.00。** 基準線最好的回答是拒答 |
70
146
  | **線上量測** | 114 條 REST 路由、151 個 element class、59 個條件主體、23 張表 / 206 個欄位 |
71
147
 
72
- `SKIPPED`、`NO_HOST`、`INCONCLUSIVE` 永遠不併進通過率。
73
- **一支把自己的盲點算成成功的掃描工具,正是這個 skill 要反對的東西。**
148
+ `SKIPPED`、`NO_HOST`、`INCONCLUSIVE` 從不折算進通過率。把自己的盲點算成成功的掃描,
149
+ 正是這個技能反對的東西。
74
150
 
75
- ### 動手之前值得先知道的兩個結果
151
+ ## 查一次要花多少
76
152
 
77
- **帶有 `group` 的屬性,單獨設定一律無效。** 兩個方向都精確:78 個無 group 的屬性得到
78
- 58 COMPILED、0 ABSENT;20 個有 group 的全部 0 COMPILED。所以 `borderLeftWidth`、
79
- `outlineColor`、`gridColumnStart` 是**同一條規則的三個實例**,不是三個各自的怪毛病。
80
- 要用就用群組形狀——`border` 收 `{width, style, color}`——或者退回 `customStyles`。
153
+ agent 要知道一個 Mosaic 節點型別或樣式鍵到底吃什麼,有三條路。同樣六個任務,用 tiktoken
154
+ 算(`tools/benchmark_tokens.py`,可以自己跑):
81
155
 
82
- **斷點覆寫只能「改變」屬性,永遠不能「移除」。** 窄螢幕的 `customStyles` 如果只是
83
- 沒寫某條框線,寬螢幕那條框線會活下來,在塌成單欄的版面正中間畫一條線。
84
- **要把 `border-left:0` 明講出來。**
156
+ | 任務 | 讀原始碼 | 整包表格載入 | `mo.py` 查詢 |
157
+ |---|---:|---:|---:|
158
+ | 放一個標題、一段文字、一顆帶連結的按鈕 | 10,005 | 259,539 | **961** |
159
+ | 設定 padding、邊框、圓角,含響應式 | 3,490 | 259,539 | **396** |
160
+ | 判斷 accordion 能不能用、怎麼嵌套 | 15,615 | 259,539 | **397** |
161
+ | 找出哪些 hover/focus 狀態真的會編譯 | 3,619 | 259,539 | **1,054** |
162
+ | 找出哪個 Mosaic 鍵驅動某條 CSS | 1,862 | 259,539 | **51** |
163
+ | commit 之前知道什麼不安全 | 63,172 | 259,539 | **288** |
85
164
 
86
- ## 工具
165
+ **比讀原始碼省 71–99.5% 的 token,比整包載入省 99.6% 以上**——而且六題裡有四題原始碼根本
166
+ 答不了:「有宣告」和「會編譯」是兩個問題,只有掃描問了第二個。表格總共 259,539 tokens;
167
+ 永遠不要整包載入,`mo.py` 才是查詢。
168
+
169
+ ### 動手前值得知道的結果
170
+
171
+ **屬於某個 `group` 的屬性單獨設定時無效。** 兩個方向都精確:78 個未分組屬性給出 58 COMPILED、
172
+ 0 ABSENT;20 個分組屬性全部 0 COMPILED。所以 `borderLeftWidth`、`outlineColor`、`gridColumnStart`
173
+ 是同一條規則的三個實例。用分組形狀——`border` 吃 `{width, style, color}`——或 `customStyles`。
174
+
175
+ **斷點覆寫可以「改」一個屬性,永遠不能「拿掉」一個。** 窄螢幕的 `customStyles` 只是不提邊框,
176
+ 寬螢幕的邊框就繼續站在那裡。把 `border-left:0` 說出口。
177
+
178
+ **只有四個型別吃 `url`**:`button`、`menu-link`、`wysiwyg-link`、`dropdown-toggle`。放在 `text`
179
+ 或 `image` 上會被接受、被存下、然後不產生任何錨點。改用 `menu-link` 包起來——它接受任意子節點,
180
+ 一有 `url` 就變成真正的 `<a href>`。
181
+
182
+ **圖片的 attachment protocol 路徑是相對 uploads 目錄的。**
183
+ `wp-attachment://image/<id>/full/2026/09/pic.png` 能解析,還會帶出附件的寬高。給它完整的
184
+ `wp-content/uploads/...` 路徑——最直覺的猜法——Mosaic 會把 uploads 前綴再接一次,而且不報錯。
185
+
186
+ ## Elementor → Mosaic
87
187
 
88
188
  ```bash
89
- wp eval-file tools/bootstrap_probe_theme.php # 免授權的測試主題
90
- python tools/build_site.py --config c.json --site sites/moksa.json
91
- python tools/verify_rwd.py --config c.json --site sites/moksa.json --csv rwd.csv
92
- python tools/copy_styles.py --config c.json --from a --to-prefix b- --only "&._m"
93
- wp eval-file tools/theme_export.php active > theme.json
94
- wp eval-file tools/theme_import.php theme.json "名稱" rebind activate
189
+ wp post meta get 2360 _elementor_data > page.json
190
+ python tools/from_elementor.py --data page.json --out spec.json --report conv.csv \
191
+ --uploads-base https://site/wp-content/uploads --slug works --post 208
192
+ python tools/build_site.py --config c.json --site spec.json
193
+ python tools/verify_conversion.py --data page.json --url https://site/works/ --report conv.csv
95
194
  ```
96
195
 
97
- `sites/_moksa.py` 是完整範例:一個真實工作室的首頁——刊頭、規格區塊、服務、九列作品表格、
98
- 流程、技術堆疊、產品、客戶見證、聯絡——**618 個節點全部透過資料表寫入**,
99
- 還有一個用具名 view timeline 做的、會跟著捲動的條款索引,完全沒有 JavaScript。
196
+ 範圍是數出來的,不是憑喜好定的:一個真實站的 19 頁裡,container / heading / text-editor /
197
+ button / html / icon-list / divider / image 佔了全部元素的 99.6%。長尾——loop grid、表單、倒數、
198
+ 第三方 addon——是動態的,沒有節點可以變成;每一個都按名稱和原因列進報告、絕不默默丟掉,
199
+ `--strict` 則拒絕產出有損的 spec。
200
+
201
+ 版面、字體、顏色、邊框、連結、圖片都會過去,三個斷點都帶(`_tablet`/`_mobile` → `_t`/`_m`)。
202
+ 不會過去的:進場動畫(Mosaic 的互動綁定未解)、shape divider、漸層疊加。驗證器接著拿建好的
203
+ 頁面對照來源——每個字串、圖片、連結、標題層級——它馬上就證明了自己的價值:抓到轉換器把
204
+ `url` 寫在不理它的節點上,漏了 21 個連結中的 19 個。
205
+
206
+ ## 工具
207
+
208
+ | 工具 | 用途 |
209
+ |---|---|
210
+ | `mo.py` | 查詢量測過的表面——**正門** |
211
+ | `build_page.py` / `build_site.py` | 透過有守門的寫入路徑 commit 一份 spec;量到會壞的一律拒絕 |
212
+ | `from_elementor.py` / `verify_conversion.py` | Elementor → Mosaic,以及內容確實到達的證明 |
213
+ | `verify_browser.py` | 瀏覽器算出來的是不是樣式表承諾的,以及有沒有通過設計稽核 |
214
+ | `verify_rwd.py` | 每條 `_t`/`_m` 宣告有沒有進到送出的樣式表 |
215
+ | `verify_intro.py` / `verify_loop.py` | 會「結束」的載入動畫;會循環、不遮東西、能打開的永續動畫 |
216
+ | `theme_export.php` / `theme_import.php` | 整個主題以 JSON 資料列透過 WP-CLI 搬移,ID 不變 |
217
+ | `theme_zip.py` / `theme_zip_compare.php` / `theme_delete.php` | 在編輯器外驅動 Mosaic 自己的 ZIP 匯出匯入、副本對來源逐樹比對、以及拒絕刪 live 主題的乾淨刪除 |
218
+ | `sweep_*.py` / `probe_*.py` | 那些表格是用這些儀器量出來的 |
219
+ | `bootstrap_probe_theme.php` / `mint_session.php` | 免授權的實驗主題,以及從 WP-CLI 鑄出 REST session |
220
+
221
+ ## 工作範例
222
+
223
+ `sites/_moksa.py` 只透過資料表建出一個真實的工作室網站,隨套件出貨作為參考:1,286 個節點的
224
+ 首頁,用具名 view timeline 做捲動追蹤的條款索引;一段一次印一塊版、把浮世繪印出來的入口動畫;
225
+ 一塊在角落永遠印下去、點了會放大的版子;還有一個 WooCommerce My Account 頁,它的 UI 全部透過
226
+ 一個跑 shortcode 的 `code` 節點進來。全程沒有自己寫任何 JavaScript。`data/` 裡每一張驗證表
227
+ 都是對著它量出來的。
228
+
229
+ ## 從哪裡開始
230
+
231
+ 1. `references/data-model.md`——頁面實際住在哪裡。
232
+ 2. `references/write-protocol.md`——checkout / check / commit。
233
+ 3. `references/failure-modes.md`——Mosaic 怎麼失敗,量測版。**動手前先讀。**
234
+ 4. `references/responsive.md`——狀態/斷點/屬性三軸。
235
+ 5. `references/styling.md`——樣式值怎麼變成 CSS。
236
+ 6. `references/design-system.md`——element class 與設計 token。
237
+
238
+ ## 發版
239
+
240
+ ```bash
241
+ npm version minor # 更新 package.json、SKILL.md 和八個平台模板的版號,
242
+ # commit、打 tag、push;tag 觸發 release.yml
243
+ ```
100
244
 
101
- ## 從哪裡開始讀
245
+ `bin/check-release.mjs` 把關每一次發版,檢查的是容易出錯而不是容易檢查的事:版號一致、
246
+ `files` 的每個 glob 都對到東西、每張驗證表的列數仍然等於 SKILL.md 和四份 README 引用的數字、
247
+ 沒有未裁定的設計稽核發現、eval 套件在場,以及**直接檢查 tarball 本身**——npm 的 `files` 白名單
248
+ 會蓋過 `.gitignore`,曾經把一個真實客戶的網站放進即將發布的套件裡。
102
249
 
103
- 1. `references/data-model.md` —— 頁面到底住在哪裡。
104
- 2. `references/write-protocol.md` —— checkout / check / commit。
105
- 3. `references/failure-modes.md` —— Mosaic 怎麼壞的,都是量出來的。**動筆前先讀。**
106
- 4. `references/responsive.md` —— state / breakpoint / property 這個軸。
107
- 5. `references/styling.md` —— 一個樣式值怎麼變成 CSS。
108
- 6. `references/design-system.md` —— element class 與設計 token。
250
+ 發布走 npm trusted publishing(OIDC):哪裡都沒有 token。npmjs.com 套件設定的 Trusted Publisher:
251
+ GitHub Actions、`Moksa1123` / `mosaic-headless`、workflow `release.yml`、environment **留空**。
109
252
 
110
253
  ## 授權
111
254
 
112
- MIT。Mosaic Pro 本身是有授權的第三方軟體,**不包含**在這個 repo 裡。
255
+ MIT。Mosaic Pro 本身是授權的第三方軟體,**不**包含在此。
package/SKILL.md CHANGED
@@ -4,7 +4,7 @@ description: |
4
4
  Build and modify Mosaic Pro (Nextend) sites by writing the underlying data model directly - no visual editor, no DOM. Query the real surface with `mo.py`, which joins every source table to the live sweeps so a lookup leads with the measured verdict rather than the declaration (122 node types, 181 properties, 98 style properties with 20 structured value shapes pinned down, 53 style states, 151 element classes, 74 dynamic variables, 12 interaction triggers, 114 REST routes, 23 tables) instead of guessing, with every node type placed on a live site one at a time and asserted against the delivered HTML, the design-token and element-class layers verified against compiled CSS, the @VAR() dynamic language verified against rendered output, nine designed pages built through the tables themselves, and the delivered pages re-read in Chromium at three viewports so a rule that is present, correct and still wrong cannot pass. Drives Mosaic's own theme export/import from outside the editor and holds the copy against the source tree for tree.
5
5
  license: "MIT"
6
6
  author: "moksa (https://moksaweb.com)"
7
- version: "1.17.0"
7
+ version: "1.17.2"
8
8
  ---
9
9
 
10
10
  # Headless Mosaic
@@ -449,6 +449,7 @@ so the pattern is in the data, not just in this paragraph.
449
449
  | `data/accordion-verification.csv` | 7 | **driven live** - the accordion family nested the way its factory requires, against the guard that refuses it unparented. Resolves two BROKE_PAGE rows |
450
450
  | `data/conversion-verification.csv` | 8 | **converted then checked live** - an Elementor page rebuilt as Mosaic and held against its source (text, images, links, heading levels), then put through rwd, browser and the design audit with every finding classified inherited-or-introduced |
451
451
  | `data/conversion-batch.csv` | 19 | **converted, built and checked live, one page after another** - every Elementor page of a production site through the converter, with per-page element and content counts |
452
+ | `data/token-benchmark.csv` | 6 | **measured with tiktoken** - the same six lookups priced three ways: reading the plugin source, loading every table, querying `mo.py`. 71-99.5% fewer tokens than the source and 99.6%+ fewer than the tables, which total 259,539 - never load them, query them |
452
453
  | `data/theme-zip-verification.csv` | 22 | **round-tripped live** - Mosaic's own ZIP export imported in test mode and compared to its source, table by table and tree by tree |
453
454
  | `data/node-type-notes.csv` | 8 | where a sweep outcome is true but misleading on its own, why. Surfaced by `mo.py type` |
454
455
  | `data/interaction-verification.csv` | 7 | **probed live** - interaction animation shapes, with negative controls and the stored row beside the payload |
@@ -622,6 +623,7 @@ post — `build_all.py` resets first for that reason.
622
623
  | tool | does |
623
624
  |---|---|
624
625
  | `mo.py` | query the measured surface - **the front door** |
626
+ | `benchmark_tokens.py` | reproduce the token figures: source vs tables vs query, six tasks |
625
627
  | `build_page.py` | commit one page spec through the verified write path |
626
628
  | `build_site.py` | a whole site: one master with the shell, one document per page |
627
629
  | `verify_rwd.py` | does every `_t`/`_m` declaration reach the served stylesheet? |
@@ -20,7 +20,7 @@
20
20
  "description": "Build and modify Mosaic Pro (Nextend) sites by writing the underlying data model directly - no visual editor, no DOM. Query the real surface (122 node types, 181 node properties, 98 style properties, 53 style states, 151 element classes, 74 dynamic variables, 114 REST routes, 23 tables) instead of guessing, with every node type placed on a live site one at a time and asserted against the delivered HTML, every style and node property swept against the compiled CSS and markup, and 569 responsive declarations checked against the stylesheet the site actually served.",
21
21
  "license": "MIT",
22
22
  "author": "moksa (https://moksaweb.com)",
23
- "version": "1.17.0"
23
+ "version": "1.17.2"
24
24
  },
25
25
  "loaderBehaviour": "Upload via Settings -> Skills -> Upload. Claude.ai parses SKILL.md frontmatter and surfaces the skill in your library. The extraction tool (extract-block-schema.php) needs a live WP-CLI connection and won't run in the sandbox; use it from a local terminal against your own site instead.",
26
26
  "uploadSteps": [
@@ -20,7 +20,7 @@
20
20
  "description": "Build and modify Mosaic Pro (Nextend) sites by writing the underlying data model directly - no visual editor, no DOM. Query the real surface (122 node types, 181 node properties, 98 style properties, 53 style states, 151 element classes, 74 dynamic variables, 114 REST routes, 23 tables) instead of guessing, with every node type placed on a live site one at a time and asserted against the delivered HTML, every style and node property swept against the compiled CSS and markup, and 569 responsive declarations checked against the stylesheet the site actually served.",
21
21
  "license": "MIT",
22
22
  "author": "moksa (https://moksaweb.com)",
23
- "version": "1.17.0"
23
+ "version": "1.17.2"
24
24
  },
25
25
  "loaderBehaviour": "Auto-loads on session start when SKILL.md frontmatter parses successfully.",
26
26
  "verified": true,
@@ -20,7 +20,7 @@
20
20
  "description": "Build and modify Mosaic Pro (Nextend) sites by writing the underlying data model directly - no visual editor, no DOM. Query the real surface (122 node types, 181 node properties, 98 style properties, 53 style states, 151 element classes, 74 dynamic variables, 114 REST routes, 23 tables) instead of guessing, with every node type placed on a live site one at a time and asserted against the delivered HTML, every style and node property swept against the compiled CSS and markup, and 569 responsive declarations checked against the stylesheet the site actually served.",
21
21
  "license": "MIT",
22
22
  "author": "moksa (https://moksaweb.com)",
23
- "version": "1.17.0"
23
+ "version": "1.17.2"
24
24
  },
25
25
  "loaderBehaviour": "Confirmed (2026-07-11): Codex CLI natively supports the SKILL.md spec. Place SKILL.md under .codex/skills/<name>/ (project) or ~/.codex/skills/<name>/ (personal) and Codex loads the name+description at session start, then the full body on demand. A parallel, broader convention .agents/skills/ (searched from cwd up to repo root, then ~/.agents/skills/) also exists across multiple tools - if your Codex CLI version prioritizes that path instead, mirror the same SKILL.md there.",
26
26
  "verified": true,
@@ -20,7 +20,7 @@
20
20
  "description": "Build and modify Mosaic Pro (Nextend) sites by writing the underlying data model directly - no visual editor, no DOM. Query the real surface (122 node types, 181 node properties, 98 style properties, 53 style states, 151 element classes, 74 dynamic variables, 114 REST routes, 23 tables) instead of guessing, with every node type placed on a live site one at a time and asserted against the delivered HTML, every style and node property swept against the compiled CSS and markup, and 569 responsive declarations checked against the stylesheet the site actually served.",
21
21
  "license": "MIT",
22
22
  "author": "moksa (https://moksaweb.com)",
23
- "version": "1.17.0"
23
+ "version": "1.17.2"
24
24
  },
25
25
  "loaderBehaviour": "CHANGED as of 2026-07-11: GitHub Copilot added a proper '.github/skills/' Agent Skills directory (December 2025), alongside the older single-file .github/copilot-instructions.md convention. This config targets the new skills-directory form. If your Copilot version predates this (pre Dec 2025), use the instructions-append fallback instead (see fallback below).",
26
26
  "fallback": {
@@ -20,7 +20,7 @@
20
20
  "description": "Build and modify Mosaic Pro (Nextend) sites by writing the underlying data model directly - no visual editor, no DOM. Query the real surface (122 node types, 181 node properties, 98 style properties, 53 style states, 151 element classes, 74 dynamic variables, 114 REST routes, 23 tables) instead of guessing, with every node type placed on a live site one at a time and asserted against the delivered HTML, every style and node property swept against the compiled CSS and markup, and 569 responsive declarations checked against the stylesheet the site actually served.",
21
21
  "license": "MIT",
22
22
  "author": "moksa (https://moksaweb.com)",
23
- "version": "1.17.0"
23
+ "version": "1.17.2"
24
24
  },
25
25
  "loaderBehaviour": "CHANGED as of 2026-07-11: Gemini CLI now natively supports the same SKILL.md standard as Claude Code and Codex CLI - the same directory-based skill works unmodified. Gemini CLI discovers skills in this precedence order: built-in, extension skills, ~/.gemini/skills/ (personal), .gemini/skills/ (project, shared via version control). At session start Gemini injects each discovered skill's name+description into the system prompt and calls activate_skill when a task matches.",
26
26
  "verified": true,
@@ -0,0 +1,7 @@
1
+ task,commands,tokens_read_source,tokens_load_tables,tokens_query,saving_vs_source_pct,saving_vs_tables_pct
2
+ "Place a heading, a paragraph and a linked button",mo.py params text ; mo.py params button ; mo.py prop url,10005,259539,961,90.4,99.63
3
+ "Set padding, a border and a radius, responsively",mo.py style --grouped ; mo.py css border-left-width ; mo.py css border-radius,3490,259539,396,88.7,99.85
4
+ "Decide whether the accordion is usable, and how to nest it",mo.py type accordion-content ; mo.py placement accordion-item,15615,259539,397,97.5,99.85
5
+ Find which hover/focus states actually compile,mo.py states --verified,3619,259539,1054,70.9,99.59
6
+ Find which Mosaic key drives one CSS property,mo.py css grid-column,1862,259539,51,97.3,99.98
7
+ Know what is unsafe before committing anything,mo.py stats ; mo.py check div text button accordion-content,63172,259539,288,99.5,99.89
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mosaic-headless",
3
- "version": "1.17.0",
3
+ "version": "1.17.2",
4
4
  "description": "AI-agent skill: build Mosaic Pro (Nextend) WordPress sites by writing the underlying data model directly - 23 custom tables, no visual editor, no DOM. Every node type, style property and node property swept against a live install and asserted on the delivered HTML and compiled CSS. Installs into Claude Code, Cursor, Codex CLI, Gemini CLI, Copilot, Continue, Windsurf and Claude.ai.",
5
5
  "keywords": [
6
6
  "wordpress",
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env python3
2
+ """benchmark_tokens.py - what this skill costs to consult, and what it saves.
3
+
4
+ pip install tiktoken
5
+ python tools/benchmark_tokens.py --mosaic-src ./mosaic --csv data/token-benchmark.csv
6
+
7
+ Every token figure in the README comes from this script. Run it yourself.
8
+
9
+ WHAT IS BEING COMPARED
10
+
11
+ An agent about to write a Mosaic page needs, for the node types it is touching:
12
+ which properties exist, what values they take, which style keys emit CSS, what may
13
+ be nested where, and which of those claims survive contact with the compiler.
14
+ Three ways to get that, priced on the same tasks:
15
+
16
+ A. READ THE SOURCE open the plugin's PHP for the types and style records the
17
+ task touches. Accurate about what is *declared*; silent
18
+ about what is measured (a declared style key that emits
19
+ nothing looks identical to one that works).
20
+ B. LOAD THE TABLES put every data/*.csv in context. Complete, and wasteful:
21
+ you pay for all of it to use one row.
22
+ C. QUERY run tools/mo.py and read back only the answer, which
23
+ leads with the measured verdict. This is what the skill
24
+ does.
25
+
26
+ HONESTY NOTES
27
+
28
+ - Token counts use tiktoken cl100k_base - OpenAI's tokenizer, not Claude's, so
29
+ absolute counts differ by roughly +-10% on Claude. The RATIOS are what matter,
30
+ and a ratio between two texts measured with the same tokenizer is stable.
31
+ - Baseline A counts exactly the files an agent would have to open to answer the
32
+ task from source: the node type's three files (factory, data, resource) plus
33
+ the style-property records and the validators those files lean on. Mosaic
34
+ spreads a type across a directory and its style surface across a hundred
35
+ small classes; the count is the files that hold the answer, not the whole
36
+ plugin - counting the whole plugin would flatter the skill.
37
+ - Baseline A also understates the real cost: source cannot answer "does this
38
+ compile?" at all, so the honest source-reading agent still has to commit and
39
+ render to find out. That round trip is not priced here.
40
+ - The mo.py outputs are captured by running the commands for real.
41
+ - Baseline B is charged once, not per task.
42
+ """
43
+ from __future__ import annotations
44
+
45
+ import argparse
46
+ import csv
47
+ import glob
48
+ import io
49
+ import os
50
+ import subprocess
51
+ import sys
52
+
53
+ try:
54
+ import tiktoken
55
+ except ImportError:
56
+ sys.exit("pip install tiktoken")
57
+
58
+ ENC = tiktoken.get_encoding("cl100k_base")
59
+ HERE = os.path.dirname(os.path.abspath(__file__))
60
+ ROOT = os.path.dirname(HERE)
61
+
62
+
63
+ def toks(text):
64
+ return len(ENC.encode(text, disallowed_special=()))
65
+
66
+
67
+ def read_all(paths):
68
+ out = ""
69
+ for p in paths:
70
+ with io.open(p, encoding="utf-8", errors="replace") as fh:
71
+ out += fh.read()
72
+ return out
73
+
74
+
75
+ def src(mosaic, *rel_globs):
76
+ paths = []
77
+ for g in rel_globs:
78
+ paths += glob.glob(os.path.join(mosaic, "Mosaic", g), recursive=True)
79
+ return sorted(set(p for p in paths if p.endswith(".php")))
80
+
81
+
82
+ def run_mo(args):
83
+ r = subprocess.run([sys.executable, os.path.join(HERE, "mo.py")] + args,
84
+ capture_output=True, text=True, encoding="utf-8", errors="replace",
85
+ cwd=ROOT)
86
+ return r.stdout
87
+
88
+
89
+ # task -> (mo.py commands, source globs that hold the declared answer)
90
+ TASKS = [
91
+ ("Place a heading, a paragraph and a linked button",
92
+ [["params", "text"], ["params", "button"], ["prop", "url"]],
93
+ ["NodeTypes/Text/*.php", "NodeTypes/Button/*.php", "NodeTypes/Wysiwyg/**/*.php",
94
+ "Validators/Validate/ValidatorURL*.php", "Validators/Validate/ValidatorAcceptedValues.php"]),
95
+ ("Set padding, a border and a radius, responsively",
96
+ [["style", "--grouped"], ["css", "border-left-width"], ["css", "border-radius"]],
97
+ ["Builder/Style/BorderRadius/*.php", "Builder/Style/CSSGrouppedPropertyFactory.php",
98
+ "Builder/Style/CSSPropertyFactory.php", "Builder/Style/CSSProperty.php",
99
+ "Builder/Style/AbstractCSSProperty.php", "Builder/Breakpoint/*.php"]),
100
+ ("Decide whether the accordion is usable, and how to nest it",
101
+ [["type", "accordion-content"], ["placement", "accordion-item"]],
102
+ ["NodeTypes/Accordion/**/*.php", "NodeTypes/ElementAbstract/*.php"]),
103
+ ("Find which hover/focus states actually compile",
104
+ [["states", "--verified"]],
105
+ ["Builder/Style/StatesMeta.php", "Builder/Style/LocalStatesMeta.php",
106
+ "Builder/Style/CSS.php"]),
107
+ ("Find which Mosaic key drives one CSS property",
108
+ [["css", "grid-column"]],
109
+ ["Builder/Style/GridArea/*.php", "Builder/Style/GridTemplate/*.php",
110
+ "Builder/Style/CSSPropertyFactory.php"]),
111
+ ("Know what is unsafe before committing anything",
112
+ [["stats"], ["check", "div", "text", "button", "accordion-content"]],
113
+ ["NodeTypes/**/*TypeFactory.php"]),
114
+ ]
115
+
116
+
117
+ def main():
118
+ ap = argparse.ArgumentParser()
119
+ ap.add_argument("--mosaic-src", help="path to the Mosaic plugin (for baseline A)")
120
+ ap.add_argument("--csv")
121
+ a = ap.parse_args()
122
+
123
+ tables = sorted(glob.glob(os.path.join(ROOT, "data", "*.csv")))
124
+ load_all = toks(read_all(tables))
125
+ print("baseline B - every table in data/ loaded at once: %s tokens (%d files)\n"
126
+ % (format(load_all, ","), len(tables)))
127
+
128
+ rows = []
129
+ print("%-58s %10s %10s %8s %8s" % ("task", "read src", "query", "vs src", "vs load"))
130
+ for label, cmds, globs in TASKS:
131
+ q = sum(toks(run_mo(c)) for c in cmds)
132
+ s = None
133
+ if a.mosaic_src:
134
+ files = src(a.mosaic_src, *globs)
135
+ s = toks(read_all(files)) if files else None
136
+ sv = (100.0 * (1 - q / s)) if s else None
137
+ lv = 100.0 * (1 - q / load_all)
138
+ print("%-58s %10s %10s %7s %7.2f%%" % (
139
+ label[:58], format(s, ",") if s else "-", format(q, ","),
140
+ "%.1f%%" % sv if sv is not None else "-", lv))
141
+ rows.append([label, " ; ".join("mo.py " + " ".join(c) for c in cmds),
142
+ s or "", load_all, q, "%.1f" % sv if sv is not None else "",
143
+ "%.2f" % lv])
144
+
145
+ if a.csv:
146
+ with open(a.csv, "w", newline="", encoding="utf-8") as fh:
147
+ w = csv.writer(fh)
148
+ w.writerow(["task", "commands", "tokens_read_source", "tokens_load_tables",
149
+ "tokens_query", "saving_vs_source_pct", "saving_vs_tables_pct"])
150
+ w.writerows(rows)
151
+ print("\nwrote", a.csv)
152
+
153
+
154
+ if __name__ == "__main__":
155
+ main()