mosaic-headless 1.16.0 → 1.17.1

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.
Files changed (34) hide show
  1. package/README.ja.md +156 -71
  2. package/README.ko.md +159 -80
  3. package/README.md +115 -70
  4. package/README.zh-TW.md +140 -61
  5. package/SKILL.md +38 -2
  6. package/assets/templates/platforms/claude-ai.json +1 -1
  7. package/assets/templates/platforms/claude-code.json +1 -1
  8. package/assets/templates/platforms/codex-cli.json +1 -1
  9. package/assets/templates/platforms/copilot.json +1 -1
  10. package/assets/templates/platforms/gemini-cli.json +1 -1
  11. package/bin/check-release.mjs +22 -0
  12. package/data/conversion-batch.csv +20 -0
  13. package/data/node-type-notes.csv +1 -1
  14. package/evals/accordion-is-not-broken/graders/criteria.md +24 -0
  15. package/evals/accordion-is-not-broken/graders/skill-fired.md +5 -0
  16. package/evals/accordion-is-not-broken/prompt.md +9 -0
  17. package/evals/build-simple-page/graders/criteria.md +23 -0
  18. package/evals/build-simple-page/graders/skill-fired.md +5 -0
  19. package/evals/build-simple-page/prompt.md +10 -0
  20. package/evals/code-node-renders-500/graders/criteria.md +23 -0
  21. package/evals/code-node-renders-500/graders/skill-fired.md +5 -0
  22. package/evals/code-node-renders-500/prompt.md +10 -0
  23. package/evals/grid-child-placement/graders/criteria.md +23 -0
  24. package/evals/grid-child-placement/graders/skill-fired.md +5 -0
  25. package/evals/grid-child-placement/prompt.md +8 -0
  26. package/evals/move-theme-safely/graders/criteria.md +21 -0
  27. package/evals/move-theme-safely/graders/skill-fired.md +5 -0
  28. package/evals/move-theme-safely/prompt.md +7 -0
  29. package/package.json +3 -2
  30. package/references/failure-modes.md +25 -0
  31. package/tools/build_page.py +20 -0
  32. package/tools/from_elementor.py +17 -0
  33. package/tools/sweep_node_types.py +9 -0
  34. package/tools/verify_conversion.py +13 -5
package/README.ja.md CHANGED
@@ -3,120 +3,205 @@
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.zh-TW.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 # 対応 8 プラットフォーム
20
+ ```
21
+
22
+ **更新は自動では起きない。** npm に新版が出ても、エージェントが読み込むフォルダは変わらない。
23
+ インストーラを `--force` 付きで再実行する(付けないと、手を入れた可能性のある SKILL.md の
24
+ 上書きを拒否する):
25
+
26
+ ```bash
27
+ npx mosaic-headless@latest claude-code --global --force
28
+ ```
29
+
30
+ Python 3 と Playwright はツールを*動かす*ときに必要で、インストールには不要。
31
+
32
+ ## これは何か
33
+
12
34
  Mosaic はページを **23 個のカスタムテーブル**に保持する。`post_content` でもなければ
13
35
  `postmeta` でもない。要素ひとつにつき 1 行、ツリー構造は `parentID` カラム、兄弟の順序は
14
- fractional-index の文字列。エディタはこのモデルの一クライアントにすぎない。
15
- フォーマットそのものではないし、使う必要もない。
36
+ fractional-index の文字列。エディタはこのモデルの一クライアントにすぎない。フォーマット
37
+ そのものではないし、なくても困らない。
16
38
 
17
- この skill はそのモデルの地図であり、**ソースを読んだものではなく、実際のインストールに
18
- 対して測定したもの**である。
39
+ このスキルはそのモデルの地図 — ソースを読んで得たものではなく、実際のインストールに対して
40
+ 計測したもの — と、モデルを通して書き、出てきたものを検証し、Elementor からページを持ち込む
41
+ ためのツール群である。
19
42
 
20
- ## すべてに優先するただ一つの規則
43
+ ## 唯一のルール
21
44
 
22
- **ノードタイプ、プロパティ名、列挙値、スタイルキー、Free/Pro の判断を記憶で書かないこと。
23
- `data/` を引くこと。**
45
+ **ノード型、プロパティ名、列挙値、スタイルキー、Free/Pro の判断を記憶から書かない。
46
+ `data/` で引く。**
24
47
 
25
- しかも grep ではなく `mo.py` で引く。grep は打った質問には答えるが、本当に抱えている
26
- 質問には答えない。`accordion-content` を grep すればその型が存在することは分かる。
27
- スイープ表は BROKE_PAGE だと言う — 置けば公開ページ全体が 54 バイトのエラー文字列に
28
- なる。どちらも本当で、どちらも間違った答えだ。行の横の注記は、その文字列が「親がない」
29
- ことを名指ししていること、`accordion > accordion-item` の下に入れ子にすれば commit も
30
- 描画も通り、キーボードで操作できる開閉 UI までついてくることを言っている。
31
- `mo.py type` はその三つを一度に見せる。
48
+ しかも grep ではなく `mo.py` で引く。grep は打った質問には答えるが、本当に抱えている質問には
49
+ 答えない。`accordion-content` を grep すればその型が存在することは分かる。スイープ表は
50
+ BROKE_PAGE だと言う — 置けば公開ページ全体が 54 バイトのエラー文字列になる。どちらも本当で、
51
+ どちらも間違った答えだ。行の横の注記は、その文字列が「親がない」ことを名指ししていること、
52
+ `accordion > accordion-item` の下に入れ子にすれば commit も描画も通り、キーボードで操作できる
53
+ 開閉 UI までついてくることを言っている。`mo.py type` はその三つを一度に見せる。
32
54
 
33
55
  ```bash
34
56
  python tools/mo.py type accordion-content # 1 つの型を、全ライブスイープに接続して表示
35
- python tools/mo.py check div text button # 危険な型・未知の型があれば exit 1
36
- python tools/mo.py style --grouped # 単独設定では必ず効かない 20 個
37
- python tools/mo.py states --verified # 実測でコンパイルされた状態だけ
38
- python tools/mo.py params text # 1 つの型に設定できるものすべて
57
+ python tools/mo.py check div text button # 安全でない/未知の型があれば 1 で終了
58
+ python tools/mo.py params text # 1 つの型に設定できるすべて
59
+ python tools/mo.py style --grouped # 単独では無効になる 20 個
60
+ python tools/mo.py states --verified # コンパイルされることが計測済みの状態
61
+ python tools/mo.py css grid-column # この CSS を出す Mosaic のキー
39
62
  ```
40
63
 
41
- そのうえでページを見る。Mosaic の失敗の仕方は 4 通りあり、
42
- **HTTP ステータスが変わるのはそのうち 1 つだけ**:
64
+ そのうえでページを見る。Mosaic には **7 つ**の失敗モードがあり、HTTP ステータスが変わるのは
65
+ そのうち 2 つだけ:
43
66
 
44
67
  ```
45
- バリデータによる正常な拒否 HTTP 200 + body に exceptions 配列
46
- commit 中の PHP fatal HTTP 500 (122 タイプ中 15、素の div の中でも起きる)
47
- 構造的に不正なノード HTTP 200、コミット済み、DB に行もある。そして
48
- 公開ページ全体が 54 バイトのエラー文字列になる
49
- 値の「形」が違う HTTP 200、保存される。ただし CSS ルールが存在しない
50
- ルールは正しいが結果が違う HTTP 200、スタイルシートにあり、内容も正しい。それでも
51
- ブラウザは別の値を計算する
52
- URL に対応するテンプレなし HTTP 406、未ログインには空の body
68
+ バリデータの正常な拒否 HTTP 200 + 本文に `exceptions` 配列
69
+ commit 中の PHP fatal HTTP 500(122 型のうち 15 が plain div の下でこうなる)
70
+ 構造的に不正なノード HTTP 200、commit 済み、DB に行あり、そして公開ページ全体が
71
+ 54 バイトのエラー文字列になる
72
+ 値の「形」が違う HTTP 200、保存済み、CSS ルールがただ出ない
73
+ ルールは正しく結果が違う HTTP 200、スタイルシートにあり、正しく、ブラウザが別の値を計算する
74
+ URL にテンプレートがない HTTP 406、未ログインには空の本文
75
+ 内容による描画時 fatal HTTP 500 — commit は通り、Mosaic がページを解析する時に死ぬ。
76
+ `code` ノードの内容はテンプレートで、minifier が書く `@media(` は
77
+ 関数呼び出しとして読まれる。`@media (` なら描画される。
78
+ build_page は前者を拒否する。
53
79
  ```
54
80
 
55
- **commit の成功も、正しいスタイルシートも、ページが動いている証拠にはならない。**
81
+ commit の成功はページが動く証拠ではないし、正しいスタイルシートもそうではない。ここの
82
+ ツールはすべて書いた後にページを取得し、5xx を空ページとして扱う — WordPress の
83
+ 「重大なエラー」画面は 2,697 バイトで、素朴な「健全なページ」の下限より大きいからだ。
56
84
 
57
85
  ## 何を、どう検証したか
58
86
 
59
- すべて実際のインストール上で実行 — WordPress 7.1、WooCommerce 11.1、Mosaic Pro 1.0.7、
87
+ すべて実際のインストールに対して実行 — WordPress 7.1、WooCommerce 11.1、Mosaic Pro 1.0.7、
60
88
  **ライセンスなし**:ライセンスが制限するのはテーマライブラリと更新であってノードファクトリ
61
- ではないため、Pro のタイプも登録され描画される。
89
+ ではないので、Pro の型も登録・描画される。
62
90
 
63
- | 対象 | 結果 |
91
+ | 項目 | 結果 |
64
92
  |---|---|
65
- | **ノードタイプ** | 122 / 122。1 ドキュメントにつき 1 タイプで commit → 描画 → 断言 → 削除。70 RENDERED、30 COMMITTED、15 COMMIT_5xx、7 BROKE_PAGE |
93
+ | **ノード型** | 122 / 122 を 1 型 1 ドキュメントでスイープ、commit → 描画 → 断言 → 削除:70 RENDERED、30 COMMITTED、15 COMMIT_5xx、7 BROKE_PAGE。描画されなかった行のうち 3 つは「必要な親なしで commit した」というスイープ手法の産物で、その旨が行の横にある |
66
94
  | **スタイルプロパティ** | 98 / 98 を実ページに書き、コンパイル済み CSS と照合:58 COMPILED、18 ABSENT、21 SKIPPED |
67
- | **ノードプロパティ** | 181 / 181 を、各プロパティ自身の validator chain から導いた値で再測定:35 APPLIED、42 NO_EFFECT、55 NO_HOST、47 SKIPPED |
95
+ | **ノードプロパティ** | 181 / 181 を各プロパティ自身のバリデータ連鎖から導いた値で再探査:35 APPLIED、42 NO_EFFECT、55 NO_HOST、47 SKIPPED |
68
96
  | **レスポンシブ** | 2 サイト計 731 件の `_t`/`_m` 宣言を、サイトが実際に配信したスタイルシートに対して 1 件ずつ断言 — 全件検証済み |
69
- | **コンポーネント** | コンポーネント機構を端から端まで実行し **8 / 8**:カテゴリ配下に作成、ドキュメントを heal、書き込み可能な instance 経由でツリーを投入、読み取り専用の方は同じ書き込みを拒否(ネガティブコントロール)、最後に 2 つのインスタンスが 1 つの定義から描画 |
70
- | **スタイル状態** | 53 状態のうち 52 を実ページに書き、テーブルが約束するセレクタと照合:**36 件が完全一致**、12 件 NO_HOST、3 件 SKIPPED、1 件 BROKE_PAGE。どの要素にも使えるグローバル 7 状態はすべて検証済み |
71
- | **インタラクション** | JS アニメーション経路を、ネガティブコントロール付きで保存行を読み戻しながら検証:`propertyMetas` は**受理され保存される**。プロパティ値は依然としてバインドされないが、その境界は厳密になった |
72
- | **入場アニメーション** | 単調時計でページ読み込み後の 15 時点をサンプリングし 8 項目を断言 — 再生されること、アニメーションする `@property` カウンタが 100 に達すること、ベールがヒットテストから外れること、ビューポート内に opacity 0 のまま取り残された要素がないこと、実際のクリックが文書に届くこと、`prefers-reduced-motion` ではベールがそもそも存在しないこと、そして全てが落ち着いた後もなお動いているものがあること。文書の初回レイアウトを待ってから始まるため、アニメーションの全くないページに対して遅れフレームは 1 つだけ |
73
- | **常時アニメーション** | 隅で刷り続け、タップすると拡大する版 — **28 項目**:一時停止したタイムラインをスクラブして周期性を証明、5 つの幅 × 25 のスクロール位置でテキスト*と*操作要素の遮蔽を「決して読めない」を失敗条件として計測、ポインタでも Enter でも開くこと、reduced motion では静止すること。Mosaic 自身のアコーディオンの上に構築 |
74
- | **アコーディオン** | `accordion-item` と `accordion-content` はスイープ表では BROKE_PAGE。ファクトリが要求する通りに入れ子にすれば commit も描画も通る、**7 件中 7 件**。注記はその行の横にある |
75
97
  | **ブラウザ** | 配信された 2 ページを Chromium の 3 つのビューポートで計算スタイル 3,988 件読み取り:2,929 件が一致、912 件は比較不能として明示、**上書き 0 件** |
76
98
  | **デザイン監査** | コントラスト、フォントフォールバック、CJK の字送り、横溢れ、テキストの切れ、1 行の文字数 — ブラウザ上で実行、**指摘 26 件、すべて理由付きで裁定済み** — 理由のない容認はリリースゲートが拒否する |
77
- | **テーマの書き出し/読み込み** | 経路は二つ、どちらも往復検証済み。`theme_export.php` は WP-CLI で行を JSON として移し、id は不変、コピーはバイト単位で同一のページを配信。`theme_zip.py` は Mosaic **自身**の ZIP 書き出し/読み込みをそのマイルストーン・プロトコルで駆動する — 読み込みは `--activate` を付けない限りテストモードに入る、既定がライブサイトの切り替えだからだ — そして **22 項目**でコピーを元とツリー単位で突き合わせる:全テーブル一致、68,337 のノード id を保持、override ノードは再採番、足りない 1 行はツリー走査が運ぶべきでない孤児 |
78
- | **実測** | REST ルート 114、element class 151、条件サブジェクト 59、23 テーブル / 206 カラム |
99
+ | **コンポーネント** | コンポーネントシステムを端から端まで駆動、**8 件中 8 件**:カテゴリの下に作成、ドキュメントが自己修復、書き込み可能インスタンス経由でツリーを充填、読み取り専用インスタンスは同じ書き込みを拒否(ネガティブコントロール)、ページ上の 2 インスタンスが 1 つの定義を 2 度描画 |
100
+ | **スタイル状態** | 53 状態のうち 52 を実ページに書き、表が約束するセレクタと照合:**36 が完全一致**、12 NO_HOST、3 SKIPPED、1 BROKE_PAGE。擬似クラスは大文字で出力される(`.M_EL9:HOVER`) |
101
+ | **インタラクション** | JS アニメーション経路をネガティブコントロール付きで探査し行を読み戻し:`propertyMetas` **は**受理・保存される;プロパティ値は依然として結び付かず、その境界は正確になった |
102
+ | **アコーディオン** | `accordion-item` と `accordion-content` はスイープ表では BROKE_PAGE。ファクトリが要求する通りに入れ子にすれば commit も描画も通る、**7 件中 7 件** |
103
+ | **入場アニメーション** | 単調時計で 15 時点をサンプリングし 8 項目を断言。文書の初回レイアウトを待ってから始まるため、アニメーションの全くないページに対して遅れフレームは 1 つだけ |
104
+ | **常時アニメーション** | 隅で刷り続け、タップすると拡大する版 — **28 項目**:一時停止したタイムラインをスクラブして周期性を証明、5 つの幅 × 25 のスクロール位置でテキスト*と*操作要素の遮蔽を「決して読めない」を失敗条件として計測、ポインタでも Enter でも開く、reduced motion では静止 |
105
+ | **Elementor 変換** | 本番サイトの全 Elementor ページ — 19 ページ、3,292 要素 — を変換・構築し、元と照合:**19 件中 19 件**、3,281 要素を運び、11 要素を明示的に除外。さらに変換後ページをレスポンシブ・ブラウザ・監査にかけ、指摘をすべて「継承」か「導入」かに分類:**導入 0 件** |
106
+ | **テーマの書き出し/読み込み** | 経路は二つ、どちらも往復検証済み。`theme_export.php` は WP-CLI で行を JSON として移し、id は不変。`theme_zip.py` は Mosaic **自身**の ZIP 書き出し/読み込みを駆動 — 読み込みは `--activate` を付けない限りテストモードに入る、既定がライブサイトの切り替えだからだ — **22 項目**でコピーを元とツリー単位で突き合わせる |
107
+ | **スキル自身** | `claude plugin eval .` — ユーザーが実際に尋ねる 5 問を各 3 回、スキルあり/なしの 2 腕、1 回ごとに LLM 審査 3 名。**あり:5 問すべて 1.00。なし:5 問すべて 0.00。** ベースラインの最善の回答は回答拒否だった |
108
+ | **ライブ計測** | REST ルート 114、element class 151、条件サブジェクト 59、23 テーブル / 206 カラム |
109
+
110
+ `SKIPPED`、`NO_HOST`、`INCONCLUSIVE` は合格率に決して繰り込まない。自らの盲点を成功として
111
+ 数えるスイープこそ、このスキルが反対しているものだ。
112
+
113
+ ### 書く前に知っておくべき結果
114
+
115
+ **`group` に属するプロパティは単独で設定しても無効。** 両方向で厳密:グループ外の 78 個は
116
+ 58 COMPILED・0 ABSENT、グループ内の 20 個はすべて 0 COMPILED。`borderLeftWidth`、`outlineColor`、
117
+ `gridColumnStart` は一つのルールの三つの例。グループの形 — `border` は `{width, style, color}` —
118
+ か `customStyles` を使う。
79
119
 
80
- `SKIPPED`、`NO_HOST`、`INCONCLUSIVE` を合格率に混ぜることは決してしない。
81
- **自分の死角を成功として数えるスイープこそ、この skill が反対しているものである。**
120
+ **ブレークポイントの上書きはプロパティを「変える」ことはできても「消す」ことはできない。**
121
+ 狭い画面の `customStyles` が単にボーダーを書かないだけなら、広い画面のボーダーは立ったままだ。
122
+ `border-left:0` と声に出す。
82
123
 
83
- ### 書き始める前に知っておく価値のある 2 つの結果
124
+ **`url` を受け取る型は 4 つだけ**:`button`、`menu-link`、`wysiwyg-link`、`dropdown-toggle`。
125
+ `text` や `image` に置くと、受理され、保存され、アンカーは一切出ない。代わりに `menu-link` で
126
+ 包む — 任意の子を取り、`url` があれば本物の `<a href>` になる。
84
127
 
85
- **`group` を持つプロパティは、単独で設定しても効かない。** 両方向とも厳密:group を持たない
86
- 78 個は 58 COMPILED / 0 ABSENT、group を持つ 20 個はすべて 0 COMPILED。つまり
87
- `borderLeftWidth`、`outlineColor`、`gridColumnStart` は 3 つの別々の奇癖ではなく
88
- **同一の規則の 3 つの実例**である。グループ形状を使うか(`border` は
89
- `{width, style, color}` を取る)、`customStyles` に落とす。
128
+ **画像の attachment protocol のパスは uploads ディレクトリからの相対。**
129
+ `wp-attachment://image/<id>/full/2026/09/pic.png` は解決され、添付の幅と高さも乗る。フルパスの
130
+ `wp-content/uploads/...` を渡すと — 一番自然な推測だが — Mosaic は uploads のベースをもう一度
131
+ 前置し、しかもエラーを出さない。
90
132
 
91
- **ブレークポイントの上書きはプロパティを「変える」ことはできても「消す」ことはできない。**
92
- 狭い画面の `customStyles` が単に境界線を書いていないだけなら、広い画面の境界線は生き残り、
93
- 1 カラムに畳まれたレイアウトの真ん中に線を引く。**`border-left:0` と明示すること。**
94
-
95
- ## ツール
133
+ ## Elementor → Mosaic
96
134
 
97
135
  ```bash
98
- wp eval-file tools/bootstrap_probe_theme.php # ライセンス不要の作業用テーマ
99
- python tools/build_site.py --config c.json --site sites/moksa.json
100
- python tools/verify_rwd.py --config c.json --site sites/moksa.json --csv rwd.csv
101
- python tools/copy_styles.py --config c.json --from a --to-prefix b- --only "&._m"
102
- wp eval-file tools/theme_export.php active > theme.json
103
- wp eval-file tools/theme_import.php theme.json "名前" rebind activate
136
+ wp post meta get 2360 _elementor_data > page.json
137
+ python tools/from_elementor.py --data page.json --out spec.json --report conv.csv \
138
+ --uploads-base https://site/wp-content/uploads --slug works --post 208
139
+ python tools/build_site.py --config c.json --site spec.json
140
+ python tools/verify_conversion.py --data page.json --url https://site/works/ --report conv.csv
104
141
  ```
105
142
 
106
- `sites/_moksa.py` が完全な実例:実在するスタジオのトップページ — マストヘッド、仕様ブロック、
107
- サービス、9 行の実績テーブル、プロセス、技術スタック、プロダクト、推薦の声、連絡先 —
108
- **618 ノードをすべてテーブル経由で commit**。名前付き view timeline で作った、スクロールに
109
- 追随する条項インデックスも含め、JavaScript は一切使っていない。
143
+ 対象範囲は好みではなく数えて決めた:実サイト 19 ページのうち、container / heading /
144
+ text-editor / button / html / icon-list / divider / image で全要素の 99.6%。ロングテール —
145
+ loop grid、フォーム、カウントダウン、サードパーティ addon — は動的で、なるべきノードがない。
146
+ それぞれ名前と理由付きで報告され、黙って落とされることはなく、`--strict` は損失のある spec の
147
+ 出力を拒否する。
148
+
149
+ レイアウト、タイポグラフィ、色、ボーダー、リンク、画像は 3 つのブレークポイントすべてで移る
150
+ (`_tablet`/`_mobile` → `_t`/`_m`)。移らないもの:入場アニメーション(Mosaic のインタラクション
151
+ 結合は未解決)、shape divider、グラデーションオーバーレイ。検証ツールは構築後のページを元と
152
+ 突き合わせ — 文字列、画像、リンク、見出しレベルをすべて — 初回から役に立った:`url` を無視する
153
+ ノードに書いていたせいで 21 リンク中 19 を落としていた変換器を捕まえた。
110
154
 
111
- ## どこから読むか
155
+ ## ツール
112
156
 
113
- 1. `references/data-model.md` — ページが実際にどこにあるか。
157
+ | ツール | 役割 |
158
+ |---|---|
159
+ | `mo.py` | 計測済みの表面を引く — **正面玄関** |
160
+ | `build_page.py` / `build_site.py` | ガード付きの書き込み経路で spec を commit;壊れると計測されたものは拒否 |
161
+ | `from_elementor.py` / `verify_conversion.py` | Elementor → Mosaic と、内容が届いたことの証明 |
162
+ | `verify_browser.py` | ブラウザがスタイルシートの約束通りに計算したか、デザイン監査に通るか |
163
+ | `verify_rwd.py` | すべての `_t`/`_m` 宣言が配信スタイルシートに届いているか |
164
+ | `verify_intro.py` / `verify_loop.py` | 「終わる」ロードアニメーション;ループし、何も隠さず、開く常時アニメーション |
165
+ | `theme_export.php` / `theme_import.php` | テーマ全体を JSON 行として WP-CLI で移動、id は不変 |
166
+ | `theme_zip.py` / `theme_zip_compare.php` / `theme_delete.php` | Mosaic 自身の ZIP 書き出し/読み込みをエディタの外から駆動、コピーを元とツリー単位で照合、ライブテーマを拒否する完全削除 |
167
+ | `sweep_*.py` / `probe_*.py` | 表を作った計測器そのもの |
168
+ | `bootstrap_probe_theme.php` / `mint_session.php` | ライセンス不要の実験用テーマと、WP-CLI から作る REST セッション |
169
+
170
+ ## 実例、ライブで
171
+
172
+ `sites/_moksa.py` はテーブルだけで本物のスタジオサイトを構築し、参照実装として同梱される。
173
+ **https://mosaic.moksaweb.com/** で公開中:1,286 ノードのホームページに、名前付き view timeline
174
+ によるスクロール追従の条項インデックス;浮世絵の版を一枚ずつ刷っていく入場シーケンス;
175
+ 隅で永遠に刷り続け、タップで拡大する版;そして UI 全体が shortcode を実行する 1 つの `code`
176
+ ノードから届く WooCommerce の [My Account](https://mosaic.moksaweb.com/my-account/) ページ。
177
+ 自前の JavaScript はどこにもない。
178
+
179
+ ## どこから始めるか
180
+
181
+ 1. `references/data-model.md` — ページが実際に住んでいる場所。
114
182
  2. `references/write-protocol.md` — checkout / check / commit。
115
- 3. `references/failure-modes.md` — Mosaic の壊れ方、すべて実測。**書く前に読む。**
116
- 4. `references/responsive.md` — state / breakpoint / property という軸。
183
+ 3. `references/failure-modes.md` — Mosaic の失敗のしかた、計測版。**書く前に読む。**
184
+ 4. `references/responsive.md` — 状態/ブレークポイント/プロパティの軸。
117
185
  5. `references/styling.md` — スタイル値が CSS になるまで。
118
186
  6. `references/design-system.md` — element class とデザイントークン。
119
187
 
188
+ ## リリース
189
+
190
+ ```bash
191
+ npm version minor # package.json、SKILL.md、8 つのプラットフォームテンプレートの版を上げ、
192
+ # commit、tag、push;tag が release.yml を起動する
193
+ ```
194
+
195
+ `bin/check-release.mjs` がすべてのリリースを門番する。検査するのは「検査しやすいこと」ではなく
196
+ 「間違えやすいこと」:版番号の一致、`files` の各 glob が何かに一致すること、各検証 CSV の行数が
197
+ SKILL.md と 4 つの README が引用する数と等しいこと、未裁定のデザイン監査指摘がないこと、eval
198
+ スイートが存在すること、そして **tarball そのものの検査** — npm の `files` 許可リストは
199
+ `.gitignore` を上書きし、かつて本物のクライアントのサイトを公開直前のパッケージに入れかけた。
200
+
201
+ 公開は npm の trusted publishing(OIDC):トークンはどこにもない。npmjs.com のパッケージ設定
202
+ Trusted Publisher にて:GitHub Actions、`Moksa1123` / `mosaic-headless`、workflow `release.yml`、
203
+ environment は**空**。
204
+
120
205
  ## ライセンス
121
206
 
122
- MIT。Mosaic Pro 自体はライセンス製品であり、この repo に**含まれていない**。
207
+ MIT。Mosaic Pro 自体はライセンスされたサードパーティ製ソフトウェアで、ここには**含まれない**。
package/README.ko.md CHANGED
@@ -3,122 +3,201 @@
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.zh-TW.md) · [日本語](README.ja.md)*
9
10
 
10
11
  ---
11
12
 
12
- Mosaic은 페이지를 **23개의 커스텀 테이블**에 보관한다. `post_content`도 아니고
13
- `postmeta`도 아니다. 요소 하나당 한 행, 트리 구조는 `parentID` 컬럼, 형제 순서는
14
- fractional-index 문자열이다. 에디터는 이 모델의 한 클라이언트일 뿐이다.
15
- 포맷 그 자체가 아니며, 반드시 쓸 필요도 없다.
13
+ ## 설치
16
14
 
17
- 이 skill은 그 모델의 지도이며, **소스를 읽은 것이 아니라 실제 설치본을 상대로
18
- 측정한 것**이다.
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 # 지원하는 8개 플랫폼
20
+ ```
21
+
22
+ **업데이트는 저절로 되지 않는다.** npm에 새 버전이 올라가도 에이전트가 읽는 폴더는 그대로다.
23
+ 인스톨러를 `--force`와 함께 다시 실행할 것(없으면 손댔을지 모르는 SKILL.md 덮어쓰기를 거부한다):
24
+
25
+ ```bash
26
+ npx mosaic-headless@latest claude-code --global --force
27
+ ```
19
28
 
20
- ## 모든 것에 우선하는 단 하나의 규칙
29
+ Python 3와 Playwright는 도구를 *실행*할 때 필요하고, 설치에는 필요 없다.
21
30
 
22
- **노드 타입, 속성 이름, 열거값, 스타일 키, Free/Pro 판단을 기억으로 쓰지 말 것.
23
- `data/`에서 찾아볼 것.**
31
+ ## 이것은 무엇인가
24
32
 
25
- 그리고 grep이 아니라 `mo.py`로 찾을 것. grep은 입력한 질문에 답할 뿐, 실제로 품고 있는
26
- 질문에는 답하지 않는다. `accordion-content`를 grep하면 그 타입이 존재한다는 것은 알 수
27
- 있다. 스윕 표는 BROKE_PAGE라고 말한다 — 하나 배치하면 공개 페이지 전체가 54바이트 에러
28
- 문자열이 된다. 둘 다 사실이고, 둘 다 틀린 답이다. 행 옆의 메모는 그 문자열이 "부모가
29
- 없다"를 지목한다는 것, `accordion > accordion-item` 아래에 중첩하면 commit도 렌더링도
30
- 되고 키보드로 조작 가능한 펼침 UI까지 따라온다는 것을 말한다. `mo.py type`은 그 셋을
31
- 한 번에 보여준다.
33
+ Mosaic은 페이지를 **23개의 커스텀 테이블**에 보관한다. `post_content`도 아니고 `postmeta`도
34
+ 아니다. 요소 하나당 한 행, 트리 구조는 `parentID` 컬럼, 형제 순서는 fractional-index 문자열이다.
35
+ 에디터는 이 모델의 한 클라이언트일 뿐이다. 포맷 자체가 아니고, 없어도 된다.
36
+
37
+ 이 스킬은 그 모델의 지도 — 소스를 읽어 얻은 것이 아니라 실제 설치에 대해 측정한 것 — 에,
38
+ 모델을 통해 쓰고, 나온 것을 검증하고, Elementor에서 페이지를 들여오는 도구를 더한 것이다.
39
+
40
+ ## 단 하나의 규칙
41
+
42
+ **노드 타입, 프로퍼티 이름, 열거값, 스타일 키, Free/Pro 판단을 기억으로 쓰지 말 것.
43
+ `data/`에서 찾을 것.**
44
+
45
+ 그리고 grep이 아니라 `mo.py`로 찾을 것. grep은 입력한 질문에 답할 뿐, 실제로 품고 있는 질문에는
46
+ 답하지 않는다. `accordion-content`를 grep하면 그 타입이 존재한다는 것은 알 수 있다. 스윕 표는
47
+ BROKE_PAGE라고 말한다 — 하나 배치하면 공개 페이지 전체가 54바이트 에러 문자열이 된다. 둘 다
48
+ 사실이고, 둘 다 틀린 답이다. 행 옆의 메모는 그 문자열이 "부모가 없다"를 지목한다는 것,
49
+ `accordion > accordion-item` 아래에 중첩하면 commit도 렌더링도 되고 키보드로 조작 가능한
50
+ 펼침 UI까지 따라온다는 것을 말한다. `mo.py type`은 그 셋을 한 번에 보여준다.
32
51
 
33
52
  ```bash
34
53
  python tools/mo.py type accordion-content # 타입 하나를 모든 라이브 스윕과 연결해 출력
35
- python tools/mo.py check div text button # 위험하거나 존재하지 않는 타입이면 exit 1
36
- python tools/mo.py style --grouped # 단독으로 설정하면 반드시 무효인 20개
37
- python tools/mo.py states --verified # 실측으로 컴파일이 확인된 상태만
38
- python tools/mo.py params text # 한 타입에 설정 가능한 모든 것
54
+ python tools/mo.py check div text button # 안전하지 않거나 모르는 타입이면 1로 종료
55
+ python tools/mo.py params text # 타입 하나에 설정 가능한 전부
56
+ python tools/mo.py style --grouped # 단독으로 설정하면 무효인 20개
57
+ python tools/mo.py states --verified # 컴파일됨이 측정된 상태들
58
+ python tools/mo.py css grid-column # 이 CSS를 내는 Mosaic 키
39
59
  ```
40
60
 
41
- 그리고 페이지를 확인한다. Mosaic의 실패 방식은 네 가지이고,
42
- **그중 HTTP 상태 코드가 바뀌는 것은 하나뿐이다**:
61
+ 그다음 페이지를 본다. Mosaic에는 **일곱 가지** 실패 모드가 있고, HTTP 상태 코드가 바뀌는 것은
62
+ 그중 둘뿐이다:
43
63
 
44
64
  ```
45
- 검증기의 정상적인 거부 HTTP 200 + body에 exceptions 배열
46
- commit 중 PHP fatal HTTP 500 (122개 타입 중 15개, 맨 div 안에서도 발생)
47
- 구조적으로 잘못된 노드 HTTP 200, 커밋됨, DB에 행도 있음. 그리고
48
- 공개 페이지 전체가 54바이트 에러 문자열이 된다
49
- 값의 "모양"이 틀림 HTTP 200, 저장됨. 다만 CSS 규칙이 존재하지 않는다
50
- 규칙은 맞는데 결과가 다름 HTTP 200, 스타일시트에 있고 내용도 맞다. 그런데 브라우저가
51
- 계산해내는 값은 다른 것이다
52
- URL에 맞는 템플릿 없음 HTTP 406, 로그인하지 않은 사용자에게는 빈 body
65
+ 검증기의 정상적 거부 HTTP 200 + 본문에 `exceptions` 배열
66
+ commit 중 PHP fatal HTTP 500 (122개 타입 중 15개가 plain div 아래에서 이렇게 됨)
67
+ 구조적으로 잘못된 노드 HTTP 200, commit됨, DB에 행 있음, 그리고 공개 페이지 전체가
68
+ 54바이트 에러 문자열이 됨
69
+ 값의 "형태"가 틀림 HTTP 200, 저장됨, CSS 규칙이 그냥 안 나옴
70
+ 규칙은 맞고 결과가 틀림 HTTP 200, 스타일시트에 있고, 맞고, 브라우저가 다른 값을 계산함
71
+ URL에 템플릿 없음 HTTP 406, 비로그인에게는 빈 본문
72
+ 내용으로 인한 렌더 시 fatal HTTP 500 — commit은 통과했고, Mosaic이 페이지를 파싱할 때 죽는다.
73
+ `code` 노드의 내용은 템플릿이라, minifier가 쓰는 `@media(`는
74
+ 함수 호출로 읽힌다. `@media (`는 렌더링된다. build_page는
75
+ 전자를 거부한다.
53
76
  ```
54
77
 
55
- **commit 성공도, 올바른 스타일시트도 페이지가 동작한다는 증거가 아니다.**
78
+ commit 성공은 페이지가 동작한다는 증거가 아니고, 올바른 스타일시트도 마찬가지다. 여기의 모든
79
+ 도구는 쓴 뒤 페이지를 가져오며, 5xx를 빈 페이지로 취급한다 — WordPress의 "치명적 오류" 화면은
80
+ 2,697바이트로, 어떤 순진한 "정상 페이지" 하한보다 크기 때문이다.
56
81
 
57
- ## 무엇을, 어떻게 검증했는가
82
+ ## 무엇을, 어떻게 검증했나
58
83
 
59
- 전부 실제 설치본에서 실행 — WordPress 7.1, WooCommerce 11.1, Mosaic Pro 1.0.7,
60
- **라이선스 없음**: 라이선스가 제한하는 것은 테마 라이브러리와 업데이트이지
61
- 노드 팩토리가 아니므로 Pro 타입도 등록되고 렌더링된다.
84
+ 전부 실제 설치에서 실행 — WordPress 7.1, WooCommerce 11.1, Mosaic Pro 1.0.7, **라이선스 없음**:
85
+ 라이선스가 막는 것은 테마 라이브러리와 업데이트지 노드 팩토리가 아니라서 Pro 타입도 등록되고
86
+ 렌더링된다.
62
87
 
63
88
  | 항목 | 결과 |
64
89
  |---|---|
65
- | **노드 타입** | 122 / 122. 문서 하나당 타입 하나로 commit → 렌더 → 단언 → 삭제. 70 RENDERED, 30 COMMITTED, 15 COMMIT_5xx, 7 BROKE_PAGE |
66
- | **스타일 속성** | 98 / 98을 실제 페이지에 쓰고 컴파일된 CSS와 대조: 58 COMPILED, 18 ABSENT, 21 SKIPPED |
67
- | **노드 속성** | 181 / 181을 각 속성 자신의 validator chain에서 도출한 값으로 재측정: 35 APPLIED, 42 NO_EFFECT, 55 NO_HOST, 47 SKIPPED |
90
+ | **노드 타입** | 122 / 122를 문서당 하나씩 스윕, commit → 렌더 → 단언 → 삭제: 70 RENDERED, 30 COMMITTED, 15 COMMIT_5xx, 7 BROKE_PAGE. 렌더되지 않은 행 중 셋은 "필요한 부모 없이 commit"한 스윕 방식의 산물이며, 그 사실이 행 옆에 적혀 있다 |
91
+ | **스타일 프로퍼티** | 98 / 98을 실제 페이지에 쓰고 컴파일된 CSS와 대조: 58 COMPILED, 18 ABSENT, 21 SKIPPED |
92
+ | **노드 프로퍼티** | 181 / 181을 각 프로퍼티 자신의 검증기 체인에서 도출한 값으로 재탐사: 35 APPLIED, 42 NO_EFFECT, 55 NO_HOST, 47 SKIPPED |
68
93
  | **반응형** | 두 사이트 합쳐 731개의 `_t`/`_m` 선언을, 사이트가 실제로 내보낸 스타일시트에 대해 하나씩 단언 — 전부 검증됨 |
69
- | **컴포넌트** | 컴포넌트 체계를 끝까지 구동해 **8 / 8**: 카테고리 아래 생성, 문서 heal, 쓰기 가능한 instance로 트리 주입, 읽기 전용 쪽은 같은 쓰기를 거부(음성 대조군), 마지막으로 인스턴스 2개가 정의 하나로 렌더링 |
70
- | **스타일 상태** | 53개 상태 중 52개를 실제 페이지에 쓰고 표가 약속한 셀렉터와 대조: **36개가 정확히 일치**, 12개 NO_HOST, 3개 SKIPPED, 1개 BROKE_PAGE. 어떤 요소에나 쓸 수 있는 전역 7개 상태는 전부 검증됨 |
71
- | **인터랙션** | JS 애니메이션 경로를 음성 대조군과 함께, 저장된 행을 되읽으며 검증: `propertyMetas`는 **수용되고 저장된다**. 속성 값은 여전히 바인딩되지 않지만 그 경계는 이제 정확하다 |
72
- | **인트로 애니메이션** | 단조 시계로 페이지 로드 후 15개 시점을 샘플링해 8가지를 단언 — 재생될 것, 애니메이션되는 `@property` 카운터가 100에 도달할 것, 베일이 히트 테스트에서 빠질 것, 뷰포트 안에 opacity 0으로 갇힌 요소가 없을 것, 실제 클릭이 문서에 닿을 것, `prefers-reduced-motion`에서는 베일이 아예 존재하지 않을 것, 그리고 모든 것이 가라앉은 뒤에도 여전히 움직이는 것이 있을 것. 문서의 첫 레이아웃을 기다린 뒤 시작하므로, 애니메이션이 전혀 없는 페이지 대비 늦은 프레임은 하나뿐 |
73
- | **상시 애니메이션** | 구석에서 계속 자기를 찍어내고 탭하면 확대되는 판 — **28개 검사**: 일시정지한 타임라인을 스크럽해 주기성 증명, 다섯 폭 × 스물다섯 스크롤 지점에서 텍스트*와* 컨트롤의 가림을 "결코 읽을 수 없음"을 실패 조건으로 측정, 포인터와 Enter 양쪽으로 열림, reduced motion에서는 정지. Mosaic 자체 아코디언 위에 구축 |
74
- | **아코디언** | `accordion-item`과 `accordion-content`는 스윕 표에서 BROKE_PAGE. 팩토리가 요구하는 대로 중첩하면 commit과 렌더링이 되며, **7건 중 7건**. 메모는 이제 그 행 옆에 있다 |
75
94
  | **브라우저** | 전달된 두 페이지를 Chromium의 세 가지 뷰포트에서 계산 스타일 3,988건 판독: 2,929건 일치, 912건은 비교 불가로 명시, **덮어쓰기 0건** |
76
95
  | **디자인 감사** | 명암비, 폰트 폴백, CJK 자간, 가로 넘침, 텍스트 잘림, 한 줄 글자 수 — 브라우저에서 실행, **지적 26건, 모두 서면으로 판정** — 이유 없는 승인은 릴리스 게이트가 거부한다 |
77
- | **테마 내보내기/가져오기** | 두 경로, 모두 왕복 검증. `theme_export.php`는 WP-CLI로 행을 JSON으로 옮기며 id는 그대로, 사본은 바이트 단위로 동일한 페이지를 제공. `theme_zip.py`는 Mosaic **자체**의 ZIP 내보내기/가져오기를 그 마일스톤 프로토콜로 구동한다 — 가져오기는 `--activate`가 없으면 테스트 모드에 놓이는데, 기본값이 라이브 사이트 전환이기 때문이다 — 그리고 **22개 검사**로 사본을 원본과 트리 단위로 대조한다: 모든 테이블 일치, 68,337개 노드 id 유지, override 노드는 재발급, 빠진 한 행은 트리 순회가 가져가면 안 되는 고아 |
78
- | **실측** | REST 라우트 114개, element class 151개, 조건 주체 59개, 23개 테이블 / 206개 컬럼 |
79
-
80
- `SKIPPED`, `NO_HOST`, `INCONCLUSIVE`를 합격률에 섞는 일은 결코 없다.
81
- **자신의 사각지대를 성공으로 세는 스윕이야말로 이 skill이 반대하는 것이다.**
96
+ | **컴포넌트** | 컴포넌트 시스템을 끝에서 끝까지 구동, **8건 중 8건**: 카테고리 아래 생성, 문서 자가 치유, 쓰기 가능 인스턴스로 트리 채움, 읽기 전용 인스턴스는 같은 쓰기를 거부(네거티브 컨트롤), 페이지의 두 인스턴스가 하나의 정의를 두 번 렌더 |
97
+ | **스타일 상태** | 53개 상태 중 52개를 실제 페이지에 쓰고 표가 약속한 셀렉터와 대조: **36개 정확히 일치**, 12 NO_HOST, 3 SKIPPED, 1 BROKE_PAGE. 의사 클래스는 대문자로 출력된다(`.M_EL9:HOVER`) |
98
+ | **인터랙션** | JS 애니메이션 경로를 네거티브 컨트롤과 함께 탐사하고 행을 읽어 되돌림: `propertyMetas`**는** 수락·저장된다; 프로퍼티 값은 여전히 바인딩되지 않으며, 그 경계는 이제 정확하다 |
99
+ | **아코디언** | `accordion-item`과 `accordion-content`는 스윕 표에서 BROKE_PAGE. 팩토리가 요구하는 대로 중첩하면 commit과 렌더링이 되며, **7건 중 7건** |
100
+ | **인트로 애니메이션** | 단조 시계로 15개 시점을 샘플링해 8가지를 단언. 문서의 첫 레이아웃을 기다린 뒤 시작하므로, 애니메이션이 전혀 없는 페이지 대비 늦은 프레임은 하나뿐 |
101
+ | **상시 애니메이션** | 구석에서 계속 자기를 찍어내고 탭하면 확대되는 판 — **28개 검사**: 일시정지한 타임라인을 스크럽해 주기성 증명, 다섯 폭 × 스물다섯 스크롤 지점에서 텍스트*와* 컨트롤의 가림을 "결코 읽을 수 없음"을 실패 조건으로 측정, 포인터와 Enter 양쪽으로 열림, reduced motion에서는 정지 |
102
+ | **Elementor 변환** | 운영 사이트의 모든 Elementor 페이지 — 19페이지, 3,292 요소 — 를 변환·구축하고 원본과 대조: **19건 중 19건**, 3,281 요소 이전, 11 요소 명시적 제외. 이어 변환된 페이지를 반응형·브라우저·감사에 통과시키고 모든 지적을 "상속"과 "도입"으로 분류: **도입 0건** |
103
+ | **테마 내보내기/가져오기** | 두 경로, 모두 왕복 검증. `theme_export.php`는 WP-CLI로 행을 JSON으로 옮기며 id는 그대로. `theme_zip.py`는 Mosaic **자체**의 ZIP 내보내기/가져오기를 구동 — 가져오기는 `--activate`가 없으면 테스트 모드에 놓이는데, 기본값이 라이브 사이트 전환이기 때문이다 — **22개 검사**로 사본을 원본과 트리 단위로 대조 |
104
+ | **스킬 자체** | `claude plugin eval .` — 사용자가 실제로 묻는 5문항을 각 3회, 스킬 있음/없음 두 팔로, 매회 LLM 심사 3명. **있음: 5문항 모두 1.00. 없음: 5문항 모두 0.00.** 베이스라인의 최선은 답변 거부였다 |
105
+ | **라이브 측정** | REST 라우트 114, element class 151, 조건 subject 59, 23 테이블 / 206 컬럼 |
106
+
107
+ `SKIPPED`, `NO_HOST`, `INCONCLUSIVE`는 결코 합격률에 섞지 않는다. 자기 사각지대를 성공으로
108
+ 세는 스윕이야말로 이 스킬이 반대하는 것이다.
109
+
110
+ ### 쓰기 전에 알아둘 결과
111
+
112
+ **`group`에 속한 프로퍼티는 단독으로 설정하면 무효다.** 양방향 모두 정확: 그룹 밖 78개는
113
+ 58 COMPILED·0 ABSENT, 그룹 안 20개는 모두 0 COMPILED. `borderLeftWidth`, `outlineColor`,
114
+ `gridColumnStart`는 한 규칙의 세 사례다. 그룹 형태 — `border`는 `{width, style, color}` — 나
115
+ `customStyles`를 쓸 것.
116
+
117
+ **브레이크포인트 오버라이드는 프로퍼티를 "바꿀" 수는 있어도 "지울" 수는 없다.** 좁은 화면의
118
+ `customStyles`가 테두리를 그저 언급하지 않으면, 넓은 화면의 테두리는 그대로 서 있다.
119
+ `border-left:0`을 소리 내어 말할 것.
120
+
121
+ **`url`을 받는 타입은 넷뿐**: `button`, `menu-link`, `wysiwyg-link`, `dropdown-toggle`.
122
+ `text`나 `image`에 두면 수락되고, 저장되고, 앵커는 전혀 나오지 않는다. 대신 `menu-link`로
123
+ 감쌀 것 — 어떤 자식이든 받고, `url`이 있으면 진짜 `<a href>`가 된다.
124
+
125
+ **이미지의 attachment protocol 경로는 uploads 디렉터리 기준 상대 경로다.**
126
+ `wp-attachment://image/<id>/full/2026/09/pic.png`는 해석되고 첨부의 가로·세로도 붙는다.
127
+ 전체 경로 `wp-content/uploads/...`를 주면 — 가장 자연스러운 추측이지만 — Mosaic이 uploads
128
+ 베이스를 한 번 더 앞에 붙이고, 에러도 내지 않는다.
129
+
130
+ ## Elementor → Mosaic
82
131
 
83
- ### 쓰기 시작하기 전에 알아둘 가치가 있는 두 가지 결과
132
+ ```bash
133
+ wp post meta get 2360 _elementor_data > page.json
134
+ python tools/from_elementor.py --data page.json --out spec.json --report conv.csv \
135
+ --uploads-base https://site/wp-content/uploads --slug works --post 208
136
+ python tools/build_site.py --config c.json --site spec.json
137
+ python tools/verify_conversion.py --data page.json --url https://site/works/ --report conv.csv
138
+ ```
84
139
 
85
- **`group`을 가진 속성은 단독으로 설정하면 동작하지 않는다.** 양방향 모두 정확하다:
86
- group이 없는 78개는 58 COMPILED / 0 ABSENT, group이 있는 20개는 전부 0 COMPILED.
87
- 즉 `borderLeftWidth`, `outlineColor`, `gridColumnStart`는 서로 다른 세 개의 기벽이
88
- 아니라 **하나의 규칙의 세 가지 사례**다. 그룹 형태를 쓰거나(`border`는
89
- `{width, style, color}`를 받는다) `customStyles`로 내려간다.
140
+ 범위는 취향이 아니라 세어서 정했다: 실제 사이트 19페이지에서 container / heading /
141
+ text-editor / button / html / icon-list / divider / image가 전체 요소의 99.6%다. 롱테일 —
142
+ loop grid, 폼, 카운트다운, 서드파티 addon — 은 동적이라 될 노드가 없다. 각각 이름과 이유와
143
+ 함께 보고되며 조용히 버려지지 않고, `--strict`는 손실 있는 spec 출력을 거부한다.
90
144
 
91
- **브레이크포인트 오버라이드는 속성을 "바꿀" 수는 있어도 "없앨" 수는 없다.**
92
- 좁은 화면의 `customStyles`가 단지 테두리를 쓰지 않았을 뿐이라면 넓은 화면의 테두리는
93
- 살아남아, 한 칼럼으로 접힌 레이아웃 한가운데에 선을 긋는다.
94
- **`border-left:0`이라고 명시적으로 말해야 한다.**
145
+ 레이아웃, 타이포그래피, 색, 테두리, 링크, 이미지가 세 브레이크포인트 모두에서 넘어간다
146
+ (`_tablet`/`_mobile` → `_t`/`_m`). 넘어가지 않는 것: 진입 애니메이션(Mosaic의 인터랙션
147
+ 바인딩은 미해결), shape divider, 그라데이션 오버레이. 검증기는 구축된 페이지를 원본과
148
+ 대조하며 — 모든 문자열, 이미지, 링크, 제목 레벨 — 첫 회부터 제 몫을 했다: `url`을 무시하는
149
+ 노드에 써서 21개 링크 중 19개를 잃던 변환기를 잡아냈다.
95
150
 
96
151
  ## 도구
97
152
 
153
+ | 도구 | 역할 |
154
+ |---|---|
155
+ | `mo.py` | 측정된 표면을 조회 — **정문** |
156
+ | `build_page.py` / `build_site.py` | 가드된 쓰기 경로로 spec을 commit; 깨진다고 측정된 것은 거부 |
157
+ | `from_elementor.py` / `verify_conversion.py` | Elementor → Mosaic, 그리고 내용이 도착했다는 증명 |
158
+ | `verify_browser.py` | 브라우저가 스타일시트의 약속대로 계산했는지, 디자인 감사를 통과하는지 |
159
+ | `verify_rwd.py` | 모든 `_t`/`_m` 선언이 전달된 스타일시트에 도달하는지 |
160
+ | `verify_intro.py` / `verify_loop.py` | "끝나는" 로드 애니메이션; 루프하고, 아무것도 가리지 않고, 열리는 상시 애니메이션 |
161
+ | `theme_export.php` / `theme_import.php` | 테마 전체를 JSON 행으로 WP-CLI를 통해 이동, id 그대로 |
162
+ | `theme_zip.py` / `theme_zip_compare.php` / `theme_delete.php` | Mosaic 자체 ZIP 내보내기/가져오기를 에디터 밖에서 구동, 사본을 원본과 트리 단위로 대조, 라이브 테마를 거부하는 깨끗한 삭제 |
163
+ | `sweep_*.py` / `probe_*.py` | 표를 만든 계측기 그 자체 |
164
+ | `bootstrap_probe_theme.php` / `mint_session.php` | 라이선스 없는 실험용 테마와 WP-CLI에서 만드는 REST 세션 |
165
+
166
+ ## 실제 예제, 라이브로
167
+
168
+ `sites/_moksa.py`는 테이블만으로 진짜 스튜디오 사이트를 구축하며 참조 구현으로 동봉된다.
169
+ **https://mosaic.moksaweb.com/** 에 올라가 있다: 1,286 노드의 홈페이지에 이름 있는 view
170
+ timeline으로 스크롤을 추적하는 조항 인덱스; 우키요에 판을 한 장씩 찍어내는 진입 시퀀스;
171
+ 구석에서 영원히 찍어내고 탭하면 확대되는 판; 그리고 UI 전체가 shortcode를 실행하는 하나의
172
+ `code` 노드로 들어오는 WooCommerce [My Account](https://mosaic.moksaweb.com/my-account/)
173
+ 페이지. 자체 JavaScript는 어디에도 없다.
174
+
175
+ ## 어디서 시작할까
176
+
177
+ 1. `references/data-model.md` — 페이지가 실제로 사는 곳.
178
+ 2. `references/write-protocol.md` — checkout / check / commit.
179
+ 3. `references/failure-modes.md` — Mosaic이 실패하는 방식, 측정판. **쓰기 전에 읽을 것.**
180
+ 4. `references/responsive.md` — 상태/브레이크포인트/프로퍼티 축.
181
+ 5. `references/styling.md` — 스타일 값이 CSS가 되기까지.
182
+ 6. `references/design-system.md` — element class와 디자인 토큰.
183
+
184
+ ## 릴리스
185
+
98
186
  ```bash
99
- wp eval-file tools/bootstrap_probe_theme.php # 라이선스 없이 쓰는 작업용 테마
100
- python tools/build_site.py --config c.json --site sites/moksa.json
101
- python tools/verify_rwd.py --config c.json --site sites/moksa.json --csv rwd.csv
102
- python tools/copy_styles.py --config c.json --from a --to-prefix b- --only "&._m"
103
- wp eval-file tools/theme_export.php active > theme.json
104
- wp eval-file tools/theme_import.php theme.json "이름" rebind activate
187
+ npm version minor # package.json, SKILL.md, 8개 플랫폼 템플릿의 버전을 올리고
188
+ # commit, tag, push; tag가 release.yml을 트리거
105
189
  ```
106
190
 
107
- `sites/_moksa.py`가 완전한 예제다. 실재하는 스튜디오의 홈페이지 — 머리기사, 사양 블록,
108
- 서비스, 9행짜리 작업 테이블, 프로세스, 기술 스택, 제품, 고객 추천사, 연락처 —
109
- **618개의 노드를 전부 테이블을 통해 commit**했고, 이름 붙인 view timeline으로 만든
110
- 스크롤 추적 조항 인덱스까지 JavaScript는 한 줄도 쓰지 않았다.
111
-
112
- ## 어디부터 읽을 것인가
191
+ `bin/check-release.mjs`가 모든 릴리스를 지킨다. 검사하는 것은 "검사하기 쉬운 것"이 아니라
192
+ "틀리기 쉬운 것": 버전 번호 일치, `files`의 각 glob이 무언가와 매칭, 각 검증 CSV의 행 수가
193
+ SKILL.md와 네 README가 인용하는 수와 같음, 판정되지 않은 디자인 감사 지적 없음, eval 스위트
194
+ 존재, 그리고 **tarball 자체 검사** — npm의 `files` 허용 목록은 `.gitignore`를 덮어쓰며, 한때
195
+ 실제 고객의 사이트를 게시 직전의 패키지에 넣을 뻔했다.
113
196
 
114
- 1. `references/data-model.md` — 페이지가 실제로 어디에 있는가.
115
- 2. `references/write-protocol.md` — checkout / check / commit.
116
- 3. `references/failure-modes.md` — Mosaic이 깨지는 방식, 전부 실측. **쓰기 전에 읽을 것.**
117
- 4. `references/responsive.md` — state / breakpoint / property 축.
118
- 5. `references/styling.md` — 스타일 값이 CSS가 되기까지.
119
- 6. `references/design-system.md` — element class와 디자인 토큰.
197
+ 게시는 npm trusted publishing(OIDC)으로: 토큰은 어디에도 없다. npmjs.com 패키지 설정의
198
+ Trusted Publisher: GitHub Actions, `Moksa1123` / `mosaic-headless`, workflow `release.yml`,
199
+ environment는 **비움**.
120
200
 
121
201
  ## 라이선스
122
202
 
123
- MIT. Mosaic Pro 자체는 라이선스가 있는 서드파티 소프트웨어이며 이 repo에
124
- **포함되어 있지 않다**.
203
+ MIT. Mosaic Pro 자체는 라이선스된 서드파티 소프트웨어이며 여기에 **포함되지 않는다**.