mosaic-headless 1.29.1 → 1.30.0

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 (42) hide show
  1. package/README.md +17 -5
  2. package/SKILL.md +43 -4
  3. package/assets/templates/platforms/claude-ai.json +1 -1
  4. package/assets/templates/platforms/claude-code.json +1 -1
  5. package/assets/templates/platforms/codex-cli.json +1 -1
  6. package/assets/templates/platforms/copilot.json +1 -1
  7. package/assets/templates/platforms/gemini-cli.json +1 -1
  8. package/data/component-wall-verification.csv +6 -0
  9. package/data/mobile-audit.csv +4 -3
  10. package/data/navigation-verification.csv +9 -9
  11. package/package.json +9 -2
  12. package/references/placement.md +31 -0
  13. package/references/responsive.md +1 -0
  14. package/scripts/check-release.mjs +7 -3
  15. package/scripts/sync-version.mjs +3 -2
  16. package/sites/_components.py +560 -0
  17. package/sites/_content.py +341 -0
  18. package/sites/_conversion.py +225 -0
  19. package/sites/_dialogs.py +579 -0
  20. package/sites/_dynamic.py +311 -0
  21. package/sites/_forms.py +487 -0
  22. package/sites/_sliders.py +437 -0
  23. package/sites/_tokens.py +277 -0
  24. package/tools/build_page.py +4 -2
  25. package/tools/build_site.py +4 -2
  26. package/tools/copy_styles.py +4 -2
  27. package/tools/mosaic_config.py +71 -0
  28. package/tools/probe.py +4 -2
  29. package/tools/probe_accordion.py +4 -2
  30. package/tools/sweep_components.py +4 -2
  31. package/tools/sweep_interactions.py +4 -2
  32. package/tools/sweep_node_properties.py +4 -2
  33. package/tools/sweep_node_types.py +4 -2
  34. package/tools/sweep_properties.py +4 -2
  35. package/tools/sweep_style_properties.py +4 -2
  36. package/tools/sweep_style_states.py +4 -2
  37. package/tools/theme_zip.py +6 -3
  38. package/tools/verify_browser.py +4 -2
  39. package/tools/verify_components.py +83 -0
  40. package/tools/verify_intro.py +4 -2
  41. package/tools/verify_rwd.py +4 -2
  42. package/sites/moksa.json +0 -62681
package/README.md CHANGED
@@ -67,17 +67,29 @@ below runs only when Claude follows `SKILL.md` and you approve the command:
67
67
  - **`tools/*.php`** run inside your own WordPress through `wp eval-file`. They read
68
68
  and write Mosaic's own tables and theme data on that site.
69
69
  - **`tools/mint_session.php`** creates a logged-in WordPress session and a matching
70
- REST nonce for an account on *your* site. You put them in a local config file
71
- (`--config sweep.json`); they are printed to your terminal and sent nowhere else.
70
+ REST nonce for an account on *your* site. They are printed to your terminal and
71
+ sent nowhere else. **Nothing here goes looking for a credential on your machine** -
72
+ you supply the session, one of two ways:
73
+ - installed as a plugin, Claude Code prompts for `Mosaic site URL`,
74
+ `WordPress logged-in cookie` and `wp_rest nonce`. The two credentials are
75
+ declared `sensitive`, so they are masked on entry and kept in your platform's
76
+ credential store, not in `settings.json`. Export them for a tool run as
77
+ `MOSAIC_SITE_URL`, `MOSAIC_REST_COOKIE` and `MOSAIC_REST_NONCE`
78
+ - or put them in a JSON file of your own and pass `--config <file>`, which is what
79
+ the examples in the skill show
80
+ The environment wins over the file, and with the environment set no credential
81
+ needs to be written to disk at all.
72
82
  - **`tools/build_site.py`, `data_upgrade.py`, `theme_zip.py`, `probe_accordion.py` and the `sweep_*.py`
73
83
  tools** call Mosaic's REST routes on the site in that config file, with that
74
84
  session, to read and write pages, components and themes. They contact only that
75
85
  site.
76
86
  - **`tools/verify_*.py`** open pages of that same site with Playwright (Chromium) or
77
87
  `urllib` and compare what was delivered with what was written.
78
- - **`sites/`** holds the worked example (the author's own site) as data. Its design
79
- loads Google Fonts, so pages built from it make visitors' browsers fetch fonts from
80
- Google, as any page using Google Fonts does.
88
+ - **`sites/`** holds the worked example (the author's own site) as readable Python
89
+ modules. `python sites/_moksa.py` generates `sites/moksa.json` from them; the
90
+ generated file is not shipped. The design loads Google Fonts, so pages built from
91
+ it make visitors' browsers fetch fonts from Google, as any page using Google Fonts
92
+ does.
81
93
 
82
94
  The tools need Python 3 and Playwright. Nothing is downloaded at install time, and no
83
95
  data is sent to the author or any third party.
package/SKILL.md CHANGED
@@ -3,8 +3,10 @@ name: "mosaic-headless"
3
3
  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 (128 node types, 191 properties, 98 style properties with 20 structured value shapes pinned down, 53 style states, 156 variants, 74 dynamic variables, 15 interaction triggers, 115 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 and its plugin data upgrade from outside the editor and holds the copy against the source tree for tree. Measured on Mosaic Pro 1.0.9: the modal (`<dialog>`), OpenStreetMap, the exit-intent and scroll-depth triggers, run rules and the memory actions all built on a live page and read back, and the 1.0.7 -> 1.0.8 -> 1.0.9 migrations driven from outside wp-admin.
5
5
  license: "MIT"
6
- author: "moksa (https://moksaweb.com)"
7
- version: "1.29.1"
6
+ metadata:
7
+ author: "moksa (https://moksaweb.com)"
8
+ version: "1.30.0"
9
+ homepage: "https://github.com/Moksa1123/mosaic-headless"
8
10
  ---
9
11
 
10
12
  # Headless Mosaic
@@ -535,8 +537,8 @@ so the pattern is in the data, not just in this paragraph.
535
537
  | `data/dialog-verification.csv` | 19 | **driven live** - the 1.0.9 modal, its triggers, its run rules and the memory actions, each exercised in a real browser |
536
538
  | `data/dialog-form-verification.csv` | 49 | **driven live** - the same three nodes made into five FORMS (sheet, drawer, corner, takeover, gate), each re-opened at 390px and 768px; also where `pickOne` on a click turned out to be a latch rather than a lottery |
537
539
  | `data/token-verification.csv` | 15 | **driven live** - every `:root` custom property declared once, non-empty, and resolved by something on the page; a token nothing points at is a token that is not working |
538
- | `data/mobile-audit.csv` | 64 | **driven live** - what a sideways-scroll check cannot see: type under the legible floor, controls too small to hit, a grid that never collapsed, content escaping its box |
539
- | `data/component-wall-verification.csv` | 20 | **driven live** - tabs switched, an accordion opened, a form's fields and Mosaic's own honeypot, and an OpenStreetMap asserted on the coordinates it was GIVEN rather than the default it falls back to |
540
+ | `data/mobile-audit.csv` | 65 | **driven live** - what a sideways-scroll check cannot see: type under the legible floor, controls too small to hit, a grid that never collapsed, content escaping its box |
541
+ | `data/component-wall-verification.csv` | 26 | **driven live** - tabs switched, an accordion opened, a form's fields and Mosaic's own honeypot, and an OpenStreetMap asserted on the coordinates it was GIVEN rather than the default it falls back to |
540
542
  | `data/form-surface-verification.csv` | 15 | **driven live** - every field type Mosaic ships in one form: select with labelled options, radio and checkbox groups, file upload, and the browser's own validation refusing an empty submit |
541
543
  | `data/content-loop-verification.csv` | 12 | **driven live** - a slider, a tab bar, a list and a paginated archive whose children all come from one authored template repeated per post; the pagination is clicked and the rows asserted to CHANGE |
542
544
  | `data/custom-fields-verification.csv` | 62 | **rendered live** - ACF and Meta Box fields of every common type read back through `@VAR` / `@LOOP` off the delivered page, loops included |
@@ -822,8 +824,41 @@ post — `build_all.py` resets first for that reason.
822
824
  | `copy_styles.py` | push one node's style onto others, by attrID or prefix |
823
825
  | `bootstrap_probe_theme.php` | a licence-free scratch theme |
824
826
  | `mint_session.php` | a matching cookie + `wp_rest` nonce from WP-CLI |
827
+ | `mosaic_config.py` | the one place a tool resolves the site URL and session - environment (`MOSAIC_SITE_URL` / `MOSAIC_REST_COOKIE` / `MOSAIC_REST_NONCE`) over `--config` file, so a credential never has to be written to disk |
825
828
  | `check_tables.py` | re-extract all eleven source-derived tables and diff them against the shipped ones - the check for a table that is not wrong but STALE, which nothing else can see |
826
829
 
830
+ ## The session, and where it comes from
831
+
832
+ Every tool that writes needs a site URL, a logged-in cookie and a matching
833
+ `wp_rest` nonce. **Never go looking for them on the user's machine** - not in a
834
+ browser profile, not in `wp-config.php`, not in a dotfile. Ask, or have the user
835
+ mint them:
836
+
837
+ ```bash
838
+ wp eval-file tools/mint_session.php # on the server, prints COOKIE= and NONCE=
839
+ ```
840
+
841
+ Then either export them, which keeps the credential off disk entirely:
842
+
843
+ ```bash
844
+ export MOSAIC_SITE_URL=https://example.com
845
+ export MOSAIC_REST_COOKIE='wordpress_logged_in_…=…'
846
+ export MOSAIC_REST_NONCE=0a0326da96
847
+ python tools/build_site.py --site sites/moksa.json
848
+ ```
849
+
850
+ or pass a JSON file the user wrote:
851
+
852
+ ```bash
853
+ python tools/build_site.py --config sweep.json --site sites/moksa.json
854
+ ```
855
+
856
+ `tools/mosaic_config.py` is the single place that resolves this, and the
857
+ environment wins over the file. Installed as a plugin, the three values are
858
+ `userConfig` options - the cookie and the nonce `sensitive: true`, so Claude Code
859
+ masks them and stores them in the platform credential store rather than in
860
+ `settings.json`.
861
+
827
862
  ## Regenerating everything
828
863
 
829
864
  ```bash
@@ -848,6 +883,10 @@ python tools/capture_live.py data/ # from data/raw/*.json +
848
883
  python tools/sweep_node_types.py --config sweep.json --setup
849
884
  python tools/sweep_node_types.py --config sweep.json --sweep --edition all
850
885
  python tools/sweep_properties.py --config sweep.json
886
+ # `sites/moksa.json` is GENERATED - the package ships the readable modules it
887
+ # comes from, not 2MB of built output. Regenerate it before anything reads it:
888
+ python sites/_moksa.py
889
+
851
890
  python tools/verify_rwd.py --config sweep.json --site sites/moksa.json --csv data/rwd-verification.csv
852
891
  python tools/verify_browser.py --config sweep.json --site sites/moksa.json \
853
892
  --csv data/browser-verification.csv --audit data/design-audit.csv
@@ -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.29.1"
23
+ "version": "1.30.0"
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.29.1"
23
+ "version": "1.30.0"
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.29.1"
23
+ "version": "1.30.0"
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.29.1"
23
+ "version": "1.30.0"
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.29.1"
23
+ "version": "1.30.0"
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,
@@ -10,6 +10,12 @@ FORM/required,PASS,required is set on 3 of them
10
10
  FORM/honeypot,PASS,"Mosaic adds its own spam trap: ['mosaic_hp_url', 'mosaic_hp_ts']"
11
11
  FORM/option_labels,SKIPPED,no select on this form
12
12
  FORM/required_blocks,PASS,"empty form reports invalid: ['name', 'email', 'message']"
13
+ NAVBAR/wide,PASS,"at this width the links are shown and the toggle is not (nav=True toggle=False, 4 links)"
14
+ NAVBAR/narrow,PASS,below buttonBreakpoint it collapses behind the toggle (dropdown-mode=True)
15
+ NAVBAR/toggle_opens,PASS,clicking it reveals the links
16
+ MEDIA/image_loaded,PASS,"1200px wide, natural 2500px"
17
+ MEDIA/icons_inline,PASS,"3 inline <svg> - `icon` takes RAW SVG in its `svg` property, not a URL or a library name"
18
+ WORDPRESS/shortcode_ran,PASS,"3 items from the shortcode - this is WooCommerce's own output, not a reimplementation"
13
19
  MULTISTEP,SKIPPED,not on this page
14
20
  COMPONENT/instances,PASS,"3 instances: ['cs-0-cs-card', 'cs-1-cs-card', 'cs-2-cs-card']"
15
21
  COMPONENT/one_definition,PASS,"all share the generated class ['_fm'] - one definition, not 3 copies"
@@ -1,7 +1,7 @@
1
1
  path,width,finding,element,detail
2
- forms/,390,GRID_NOT_COLLAPSED,mk-f-links,2 columns at 390px
3
2
  components/,390,GRID_NOT_COLLAPSED,cp-tabs-menu,2 columns at 390px
4
3
  components/,390,GRID_NOT_COLLAPSED,mk-f-links,2 columns at 390px
4
+ forms/,390,GRID_NOT_COLLAPSED,mk-f-links,2 columns at 390px
5
5
  content/,390,GRID_NOT_COLLAPSED,mk-f-links,2 columns at 390px
6
6
  dialogs/,390,GRID_NOT_COLLAPSED,mk-f-links,2 columns at 390px
7
7
  sliders/,390,SMALL_TARGET,sl-main-nav-dot,34x28:
@@ -18,10 +18,11 @@ sliders/,390,GRID_NOT_COLLAPSED,mk-f-links,2 columns at 390px
18
18
  tokens/,390,GRID_NOT_COLLAPSED,mk-f-links,2 columns at 390px
19
19
  dynamic/,390,GRID_NOT_COLLAPSED,mk-f-links,2 columns at 390px
20
20
  conversion/,390,GRID_NOT_COLLAPSED,mk-f-links,2 columns at 390px
21
- forms/,768,LONG_MEASURE,m-text,59 chars/line
22
- forms/,768,LONG_MEASURE,m-text,59 chars/line
23
21
  components/,768,LONG_MEASURE,m-text,44 chars/line
24
22
  components/,768,LONG_MEASURE,m-text,56 chars/line
23
+ components/,768,LONG_MEASURE,m-text,56 chars/line
24
+ forms/,768,LONG_MEASURE,m-text,59 chars/line
25
+ forms/,768,LONG_MEASURE,m-text,59 chars/line
25
26
  content/,768,LONG_MEASURE,m-text,43 chars/line
26
27
  content/,768,LONG_MEASURE,m-text,58 chars/line
27
28
  dialogs/,768,LONG_MEASURE,m-text,46 chars/line
@@ -1,18 +1,18 @@
1
1
  check,result,detail
2
2
  FRONT_PAGE,PASS,"HTTP 200, 240832 bytes"
3
3
  MENU_IS_LINKS,PASS,1 real <a href> in #mk-nav
4
- NAV 首頁,PASS,"/ -> HTTP 200, 240832 bytes"
5
- PAGE moksa,PASS,"HTTP 200, 240833 bytes"
6
- PAGE my-account,PASS,"HTTP 200, 101037 bytes"
4
+ NAV 首頁,PASS,"/ -> HTTP 200, 240834 bytes"
5
+ PAGE moksa,PASS,"HTTP 200, 240832 bytes"
6
+ PAGE my-account,PASS,"HTTP 200, 101036 bytes"
7
7
  PAGE intro,PASS,"HTTP 200, 134746 bytes"
8
8
  PAGE content,PASS,"HTTP 200, 102378 bytes"
9
9
  PAGE forms,PASS,"HTTP 200, 126731 bytes"
10
- PAGE components,PASS,"HTTP 200, 119522 bytes"
11
- PAGE conversion,PASS,"HTTP 200, 103205 bytes"
12
- PAGE dynamic,PASS,"HTTP 200, 112622 bytes"
13
- PAGE tokens,PASS,"HTTP 200, 125794 bytes"
14
- PAGE sliders,PASS,"HTTP 200, 151809 bytes"
15
- PAGE dialogs,PASS,"HTTP 200, 207347 bytes"
10
+ PAGE components,PASS,"HTTP 200, 135123 bytes"
11
+ PAGE conversion,PASS,"HTTP 200, 103204 bytes"
12
+ PAGE dynamic,PASS,"HTTP 200, 113003 bytes"
13
+ PAGE tokens,PASS,"HTTP 200, 125413 bytes"
14
+ PAGE sliders,PASS,"HTTP 200, 152191 bytes"
15
+ PAGE dialogs,PASS,"HTTP 200, 207349 bytes"
16
16
  ANCHORS moksa,PASS,every in-page link resolves
17
17
  ANCHORS my-account,PASS,every in-page link resolves
18
18
  ANCHORS intro,PASS,every in-page link resolves
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mosaic-headless",
3
- "version": "1.29.1",
3
+ "version": "1.30.0",
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",
@@ -35,7 +35,14 @@
35
35
  "data/*.csv",
36
36
  "references/",
37
37
  "sites/_moksa.py",
38
- "sites/moksa.json",
38
+ "sites/_components.py",
39
+ "sites/_content.py",
40
+ "sites/_conversion.py",
41
+ "sites/_dialogs.py",
42
+ "sites/_dynamic.py",
43
+ "sites/_forms.py",
44
+ "sites/_sliders.py",
45
+ "sites/_tokens.py",
39
46
  "tools/*.py",
40
47
  "tools/*.php",
41
48
  "SKILL.md",
@@ -153,3 +153,34 @@ source the column was derived from rather than the one it was convenient to read
153
153
  an `*ElementMResourceFactory.php`; every extractor walked
154
154
  `*ElementTypeFactory.php` only, so they were absent from the catalog and a
155
155
  fieldset appeared to have no inner structure.
156
+
157
+ ## navbar, media and the two WordPress nodes
158
+
159
+ ```
160
+ navbar animation (none|fade|fadeDown|fadeUp|scaleDown|flipDown)
161
+ buttonBreakpoint (a breakpoint id, or "none")
162
+ disableScroll
163
+ navbar-toggle the button that appears BELOW the breakpoint
164
+ navigation the links, which collapse behind it
165
+ ```
166
+
167
+ Measured: above `buttonBreakpoint` the links are shown and the toggle is hidden;
168
+ below it the host gains `dropdown-mode`, the links disappear, and clicking the
169
+ toggle adds `dropdown--opened` and reveals them. The breakpoint is named with the
170
+ same ids the style system uses (`_t`, `_m`).
171
+
172
+ **`icon` takes RAW SVG** in its `svg` property — not a URL, not an icon-library
173
+ name. `autoScale` makes it follow the element's box, and `currentColor` inside the
174
+ SVG picks up the node's `color`.
175
+
176
+ **`image.image` takes the attachment protocol**:
177
+ `wp-attachment://image/<id>/full/<uploads-relative path>`. The last segment is
178
+ relative to the uploads directory and a full path is silently doubled; a plain URL
179
+ also works and simply skips the width/height resolution.
180
+
181
+ **`wpShortcode.shortcode`** runs a real shortcode — `[products limit="3"]` renders
182
+ WooCommerce's own markup, not a reimplementation — and **`wpContent.content`** is a
183
+ `ValidatorDynamicCodeObject`, so it takes the `{"v": "…"}` form; a bare string is
184
+ dropped and the block renders empty. Its default is `@VAR('post/content')`, which
185
+ is why the node usually sits inside a loop and needs nothing written at all, and
186
+ the property is gated on the `unfiltered_html` capability.
@@ -136,6 +136,7 @@ mechanical check reads what the spec declared and what the site actually served,
136
136
  compares them key by key:
137
137
 
138
138
  ```bash
139
+ python sites/_moksa.py # moksa.json is generated, not shipped
139
140
  python tools/verify_rwd.py --config sweep.json --site sites/moksa.json --csv rwd.csv
140
141
  ```
141
142
 
@@ -35,7 +35,11 @@ const pkg = JSON.parse(read("package.json"));
35
35
  const want = pkg.version;
36
36
 
37
37
  const skill = read("SKILL.md");
38
- const fmVersion = /^version:\s*"?([^"\n]+)"?\s*$/m.exec(skill.split("---")[1] || "");
38
+ // `version` sits under `metadata:`, hence the leading indent. The Agent Skills
39
+ // spec allows only allowed-tools / compatibility / description / license /
40
+ // metadata / name at the top level, and a stray `version` there does not get
41
+ // ignored - it fails the upload.
42
+ const fmVersion = /^\s+version:\s*"?([^"\n]+)"?\s*$/m.exec(skill.split("---")[1] || "");
39
43
  if (!fmVersion) fail("SKILL.md frontmatter has no version:");
40
44
  else if (fmVersion[1].trim() !== want)
41
45
  fail(`SKILL.md version ${fmVersion[1].trim()} != package.json ${want}`);
@@ -101,8 +105,8 @@ const counts = {
101
105
  "data/dialog-verification.csv": 19,
102
106
  "data/slider-verification.csv": 30,
103
107
  "data/token-verification.csv": 15,
104
- "data/mobile-audit.csv": 64,
105
- "data/component-wall-verification.csv": 20,
108
+ "data/mobile-audit.csv": 65,
109
+ "data/component-wall-verification.csv": 26,
106
110
  "data/form-surface-verification.csv": 15,
107
111
  "data/content-loop-verification.csv": 12,
108
112
  "data/navigation-verification.csv": 36,
@@ -38,12 +38,13 @@ const head = skill.slice(0, end);
38
38
  const rest = skill.slice(end);
39
39
  // "already correct" and "line missing" are different answers, and conflating them
40
40
  // made this exit 1 on a release where the version happened to match already.
41
- const VERSION_LINE = /^version:\s*"?[^"\n]*"?\s*$/m;
41
+ // indented: it lives under `metadata:` (see check-release.mjs for why)
42
+ const VERSION_LINE = /^(\s+)version:\s*"?[^"\n]*"?\s*$/m;
42
43
  if (!VERSION_LINE.test(head)) {
43
44
  console.error("SKILL.md frontmatter has no version: line to update");
44
45
  process.exit(1);
45
46
  }
46
- const next = head.replace(VERSION_LINE, `version: "${v}"`);
47
+ const next = head.replace(VERSION_LINE, (_m, indent) => `${indent}version: "${v}"`);
47
48
  if (next !== head) {
48
49
  fs.writeFileSync(skillPath, next + rest);
49
50
  touched.push("SKILL.md");