mosaic-headless 1.17.2 → 1.19.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 (67) hide show
  1. package/README.ja.md +19 -10
  2. package/README.ko.md +19 -10
  3. package/README.md +18 -9
  4. package/README.zh-TW.md +17 -9
  5. package/SKILL.md +118 -40
  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 +2 -1
  12. package/data/accordion-verification.csv +1 -1
  13. package/data/browser-verification.csv +906 -906
  14. package/data/conversion-verification.csv +1 -1
  15. package/data/custom-fields-verification.csv +63 -0
  16. package/data/db-columns.csv +40 -36
  17. package/data/design-audit-acknowledged.csv +1 -0
  18. package/data/design-audit.csv +1 -1
  19. package/data/intro-verification.csv +7 -7
  20. package/data/loop-verification.csv +5 -5
  21. package/data/node-properties.csv +4 -3
  22. package/data/node-property-verification.csv +81 -80
  23. package/data/node-type-notes.csv +1 -1
  24. package/data/node-types.csv +9 -9
  25. package/data/node-verification.csv +100 -100
  26. package/data/rest-routes.csv +1 -1
  27. package/data/style-properties.csv +1 -1
  28. package/data/style-state-verification.csv +48 -48
  29. package/data/style-states.csv +32 -32
  30. package/data/style-verification.csv +1 -1
  31. package/data/theme-zip-verification.csv +10 -10
  32. package/data/variants.csv +153 -0
  33. package/evals/accordion-is-not-broken/graders/criteria.md +1 -1
  34. package/evals/grid-child-placement/graders/criteria.md +3 -3
  35. package/package.json +1 -1
  36. package/references/custom-fields.md +112 -0
  37. package/references/data-model.md +7 -6
  38. package/references/design-system.md +28 -16
  39. package/references/interactions.md +1 -1
  40. package/references/responsive.md +6 -6
  41. package/references/styling.md +23 -14
  42. package/references/upgrading.md +182 -0
  43. package/references/vs-elementor-gutenberg.md +1 -1
  44. package/references/write-protocol.md +1 -1
  45. package/sites/_moksa.py +67 -62
  46. package/sites/moksa.json +219 -219
  47. package/tools/bootstrap_probe_theme.php +1 -1
  48. package/tools/build_page.py +17 -11
  49. package/tools/build_site.py +1 -1
  50. package/tools/capture_live.py +42 -6
  51. package/tools/data_upgrade.py +74 -0
  52. package/tools/extract_node_types.py +1 -1
  53. package/tools/from_elementor.py +3 -3
  54. package/tools/list_fields.php +88 -0
  55. package/tools/mo.py +10 -9
  56. package/tools/probe.py +1 -1
  57. package/tools/sweep_components.py +1 -1
  58. package/tools/sweep_interactions.py +3 -3
  59. package/tools/sweep_node_properties.py +1 -1
  60. package/tools/sweep_node_types.py +20 -0
  61. package/tools/sweep_style_properties.py +6 -7
  62. package/tools/sweep_style_states.py +2 -2
  63. package/tools/theme_export.php +2 -2
  64. package/tools/theme_zip_compare.php +2 -2
  65. package/tools/verify_browser.py +6 -5
  66. package/tools/verify_rwd.py +17 -15
  67. package/data/element-classes.csv +0 -152
package/SKILL.md CHANGED
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: "mosaic-headless"
3
3
  description: |
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.
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, 182 properties, 98 style properties with 20 structured value shapes pinned down, 53 style states, 152 variants, 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 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.8, with the 1.0.7 -> 1.0.8 migration (variants, universal classes, `m-` class names, customDeclarations) run and re-verified.
5
5
  license: "MIT"
6
6
  author: "moksa (https://moksaweb.com)"
7
- version: "1.17.2"
7
+ version: "1.19.0"
8
8
  ---
9
9
 
10
10
  # Headless Mosaic
@@ -48,7 +48,7 @@ python tools/mo.py css border-radius # which Mosaic key drives this CSS
48
48
  python tools/mo.py states --grep hover # state IDs and their selector templates
49
49
  python tools/mo.py states --verified # only the ones measured to compile
50
50
  python tools/mo.py vars --namespace post # the @VAR() surface
51
- python tools/mo.py classes --grep Heading # element classes (theme-global)
51
+ python tools/mo.py classes --grep Heading # variants: the theme-global element classes
52
52
  python tools/mo.py routes --grep template
53
53
  python tools/mo.py tables wp_mosaic_nodes
54
54
  python tools/mo.py skeleton # a minimal valid page spec
@@ -58,7 +58,7 @@ python tools/mo.py skeleton # a minimal valid page spec
58
58
  measured verdict rather than the declaration, because on this platform the two
59
59
  disagree for 52 of the 122 types.
60
60
 
61
- Then check the page. Mosaic has four failure modes and **only one of them changes the
61
+ Then check the page. Mosaic has eight failure modes and **only three of them change the
62
62
  HTTP status code**:
63
63
 
64
64
  ```
@@ -78,6 +78,12 @@ CONTENT when Mosaic parses it. A `code` node's content is a
78
78
  template: `@media(` (as every minifier writes it) is
79
79
  read as a function call and kills the whole page.
80
80
  `@media (` renders. build_page refuses the former.
81
+ plugin upgrade renamed HTTP 200, page intact, rows migrated - and a selector
82
+ what the page emits you wrote against an emitted class matches nothing.
83
+ 1.0.8: `M_EL4 M_EL_Div` -> `m-div _e`, the state
84
+ classes with them, `customStyles` -> `customDeclarations`.
85
+ The worked example's tap-to-enlarge stopped opening
86
+ and only `verify_loop.py` OPENS said so.
81
87
  ```
82
88
 
83
89
  That last one also fooled every checker here for a day: WordPress answers a fatal
@@ -125,8 +131,11 @@ measured.
125
131
  ## What was verified, and how
126
132
 
127
133
  Everything ran against a live install: WordPress 7.1, WooCommerce 11.1,
128
- Mosaic Pro 1.0.7, **unlicensed** — the licence gates the theme library and updates,
129
- not the node factories, so the Pro types register and render regardless.
134
+ Mosaic Pro 1.0.8, **unlicensed** — the licence gates the theme library and updates,
135
+ not the node factories, so the Pro types register and render regardless. The
136
+ site was built on 1.0.7 and upgraded in place: the 1.0.8 data migration
137
+ (`tools/data_upgrade.py`) ran over the real theme, and every sweep below was
138
+ re-run on the result, so the tables describe 1.0.8 as delivered, not as declared.
130
139
 
131
140
  ```
132
141
  WRITE PATH verified end to end over REST
@@ -135,10 +144,15 @@ WRITE PATH verified end to end over REST
135
144
  NODE SWEEP 122 of 122 node types, ONE PER DOCUMENT, committed then rendered then
136
145
  deleted, asserting each type's attrID against the delivered HTML:
137
146
  RENDERED 70 id found; tag and classes recorded
138
- COMMITTED 30 row exists, nothing reached the page
147
+ COMMITTED 25 row exists, nothing reached the page
139
148
  COMMIT_5xx 15 PHP fatal on commit
140
- BROKE_PAGE 7 committed, then the whole page died
141
- Free 74: 43/22/6/3. Pro 48: 27/8/9/4. data/node-verification.csv
149
+ BROKE_PAGE 12 committed, then the whole page died
150
+ Free 74: 43/18/6/7. Pro 48: 27/7/9/5. data/node-verification.csv
151
+ Re-swept on 1.0.8: five types that used to commit and render
152
+ nothing (accordion-title, loop-pagination and its two buttons,
153
+ multi-steps-form-step) now kill the page instead - the render
154
+ guard on their required parent throws. Same advice, louder
155
+ failure: nest them. `button` now renders `<button>`, not `<span>`.
142
156
  Three of the non-rendering outcomes are artefacts of the sweep's own
143
157
  method - one type per document, under a plain div - and not of the
144
158
  type: `component-instance` (needs `component-instance/<id>`),
@@ -150,12 +164,23 @@ STYLE the states[state][breakpoint][property] shape confirmed by writing
150
164
  and reading back the compiled CSS; 20 of 22 structured value shapes
151
165
  pinned down the same way. data/style-value-shapes.csv
152
166
 
153
- DESIGN SYS element classes and collection variables both verified against compiled
154
- CSS: an elementClass on the Heading 2 meta emitted a site-wide
155
- h2,.M_EL_Text__Heading2{...} rule, and a collection variable emitted
167
+ DESIGN SYS variants and collection variables both verified against compiled
168
+ CSS: a variant on the Heading 2 catalog entry emitted a site-wide
169
+ h2,.m-heading-2{...} rule (1.0.7: h2,.M_EL_Text__Heading2), and a
170
+ collection variable emitted
156
171
  :root{--brand: rgb(9, 99, 199)} with background-color:var(--brand).
157
172
  references/design-system.md
158
173
 
174
+ FIELDS ACF 6.8 and Meta Box 5.15, forty fields registered in code on a page,
175
+ read back off the delivered HTML: 59 of 62 expressions resolve to the
176
+ value expected, the 3 empties explained (select label needs ACF's
177
+ array format; oEmbed dies in a text node, lives in a code node).
178
+ Loops over relationship / checkbox / taxonomy / gallery / clone /
179
+ group fields rendered exactly the rows the field holds. The names
180
+ are not guessable (meta_k, meta_k__label, loopk, loop-k for a
181
+ group, item/value_sub) - tools/list_fields.php prints them.
182
+ data/custom-fields-verification.csv, references/custom-fields.md
183
+
159
184
  DYNAMIC the @VAR('namespace/name') language verified against rendered output -
160
185
  @VAR('post/title') produced the real post title, @concat/@substr/
161
186
  @fallback all compose over it. 74 variables in
@@ -167,9 +192,10 @@ PROPERTIES 170 probes over the declared property surface, each value asserted
167
192
 
168
193
  STATES 52 of the 53 style states written to a live page and matched against
169
194
  the selector `data/style-states.csv` promises: 36 COMPILED exactly,
170
- 1 BROKE_PAGE, 12 NO_HOST (their node type cannot be committed safely),
171
- 3 SKIPPED. All seven globally usable states verified - and the
172
- pseudo-classes are emitted UPPERCASE (`.M_EL9:HOVER`), so grepping a
195
+ 13 NO_HOST (their node type cannot be committed safely), 3 SKIPPED,
196
+ 0 BROKE_PAGE on 1.0.8 (the one host that broke the page on 1.0.7
197
+ now refuses to commit alone, so it is NO_HOST). All seven globally usable states verified - and the
198
+ pseudo-classes are emitted UPPERCASE (`._j:HOVER`), so grepping a
173
199
  stylesheet for `:hover` finds nothing. data/style-state-verification.csv
174
200
 
175
201
  COMPONENTS used on the example page: the service row is committed ONCE to the
@@ -310,10 +336,17 @@ EVAL the skill itself, put in front of the model with and without it loa
310
336
  don't have reliable knowledge of Mosaic Pro's spec format"), which is
311
337
  the right thing for a model with no data to do. evals/
312
338
 
313
- MEASURED 114 REST routes, 151 element classes, 59 condition subjects,
314
- 23 tables / 206 columns - read off the running site.
339
+ MEASURED 114 REST routes, 152 variants, 59 condition subjects,
340
+ 23 tables / 210 columns - read off the running site.
341
+
342
+ UPGRADE 1.0.7 -> 1.0.8 driven over the plugin's own milestone route from
343
+ outside wp-admin (6 milestones, 17 calls): four class tables renamed,
344
+ every emitted class name changed (M_EL4 M_EL_Div -> m-div _e),
345
+ customStyles -> customDeclarations in every stored row - and the
346
+ one selector on the worked example that named an emitted class
347
+ stopped matching until it was rewritten. references/upgrading.md
315
348
 
316
- FROM SOURCE 122 node types, 181 properties (61 with enums), 207 pluggable IDs,
349
+ FROM SOURCE 122 node types, 182 properties (61 with enums), 207 pluggable IDs,
317
350
  122 placement rules, 10 composite default structures, 98 style
318
351
  properties, 53 style states.
319
352
  ```
@@ -324,8 +357,8 @@ the difference is worth being exact about:
324
357
 
325
358
  ```
326
359
  node types 122 / 122 swept live, one per document
327
- node properties 181 / 181 re-probed with a value shaped by each property's
328
- own validator chain: 35 APPLIED, 42 NO_EFFECT,
360
+ node properties 182 / 182 re-probed with a value shaped by each property's
361
+ own validator chain: 35 APPLIED, 43 NO_EFFECT,
329
362
  2 EDITOR_ONLY, 55 NO_HOST (no rendering type
330
363
  declares them), 2 INSTRUMENT, 45 SKIPPED
331
364
  style properties 98 / 98 swept live; 58 COMPILED, 18 ABSENT, 1 NO_ELEMENT,
@@ -334,7 +367,7 @@ style properties 98 / 98 swept live; 58 COMPILED, 18 ABSENT, 1 NO_ELEMENT,
334
367
  ```
335
368
 
336
369
  **A same-value probe measures the probe, not the surface.** The first property run
337
- sent the string `MPROP0000X` to all 181 properties regardless of what each wanted,
370
+ sent the string `MPROP0000X` to all 181 properties (182 since 1.0.8) regardless of what each wanted,
338
371
  and reported 91 NO_EFFECT. The tell was that `tagName` was in that list while the
339
372
  entire demo site is built on it. Re-probed with a value derived from the declared
340
373
  validator chain - array for `ValidatorArray`, boolean for `ValidatorBoolean`, a legal
@@ -351,7 +384,8 @@ located by `attrID`. They are labelled **INSTRUMENT**, which is not a pass eithe
351
384
  and the label carries the file and row count that does cover them.
352
385
 
353
386
  **Some properties are gated by a companion.** `target` and `rel` did nothing until
354
- the node also carried a `url`: `button` and `menu-link` render a `<span>` without one
387
+ the node also carried a `url`: `menu-link` renders a `<span>` without one (and so did
388
+ `button` before 1.0.8; it is a `<button>` now)
355
389
  and an `<a href>` with it, so an anchor-only attribute has nothing to attach to. A
356
390
  NO_EFFECT is only meaningful once the property has been given the context it needs.
357
391
 
@@ -371,13 +405,13 @@ Zero exceptions in either direction. The 20 are the `borderStyle` per-side longh
371
405
  (12), `outlineStyle` (4) and `gridChildPosition` (4) — so `borderLeftWidth`,
372
406
  `outlineColor` and `gridColumnStart` are all instances of one rule rather than three
373
407
  oddities. Set the grouped shape instead (`border` takes `{width, style, color}`), or
374
- use `customStyles`. `data/style-verification.csv` carries the group beside the result
408
+ use `customDeclarations`. `data/style-verification.csv` carries the group beside the result
375
409
  so the pattern is in the data, not just in this paragraph.
376
410
 
377
411
  **Known gaps, stated rather than papered over.**
378
412
 
379
413
  - `backgroundStyle` is the one style property whose shape resisted every attempt — it
380
- accepts what you send and emits `background-image:none`. Use `customStyles` for
414
+ accepts what you send and emits `background-image:none`. Use `customDeclarations` for
381
415
  gradients, as the glass and darkglow pages do.
382
416
  - **`gridColumnStart` / `gridColumnEnd` / `gridRowStart` / `gridRowEnd` emit nothing.**
383
417
  All four are real entries in `data/style-properties.csv`, under `gridChildPosition`.
@@ -412,7 +446,7 @@ so the pattern is in the data, not just in this paragraph.
412
446
  6. `references/responsive.md` - the state/breakpoint/property axis, the two
413
447
  breakpoint rows, and the override that can change a property but never remove
414
448
  one. **Read before writing any `_t` or `_m` value.**
415
- 7. `references/design-system.md` — element classes and design tokens. **Read this
449
+ 7. `references/design-system.md` — variants (element classes) and design tokens. **Read this
416
450
  before styling anything beyond a one-off page**; per-node style is the wrong layer
417
451
  for a real site.
418
452
  8. `references/dynamic-content.md` — the `@VAR()` language. The syntax is not
@@ -422,6 +456,12 @@ so the pattern is in the data, not just in this paragraph.
422
456
  10. `references/interactions.md` — the JavaScript animation system, and how far it is
423
457
  verified.
424
458
  11. `references/vs-elementor-gutenberg.md` — which builder habits transfer.
459
+ 12. `references/custom-fields.md` — ACF and Meta Box fields as `@VAR` / `@LOOP`:
460
+ the names, what each field type resolves to, and the loop element that walks
461
+ a multi-value field. Measured, 62 rows.
462
+ 13. `references/upgrading.md` — what a plugin update does to the data and to the
463
+ delivered page, measured on 1.0.7 -> 1.0.8; how to drive the migration and what
464
+ to re-verify afterwards.
425
465
 
426
466
  ## The data files
427
467
 
@@ -429,14 +469,14 @@ so the pattern is in the data, not just in this paragraph.
429
469
  |---|---|---|
430
470
  | `data/node-verification.csv` | 122 | **swept live** — outcome, rendered tag and classes, page bytes, failure detail |
431
471
  | `data/node-types.csv` | 122 | source — slug, label, edition, aliases, data class |
432
- | `data/node-properties.csv` | 181 | source — property, validator chain, **accepted enum values**, `supportsInherit` |
472
+ | `data/node-properties.csv` | 182 | source — property, validator chain, **accepted enum values**, `supportsInherit` |
433
473
  | `data/placement-rules.csv` | 122 | source — which children each type accepts |
434
474
  | `data/default-children.csv` | 10 | source — what a composite type needs **inside** it |
435
475
  | `data/style-properties.csv` | 98 | source — every settable CSS property and its value shape |
436
476
  | `data/style-value-shapes.csv` | 22 | **probed live** — the exact JSON shape for each structured value, and what it compiled to |
437
477
  | `data/style-states.csv` | 53 | source — state IDs with their exact CSS selector templates |
438
478
  | `data/property-verification.csv` | 170 | **probed live** — per-property effect on markup vs CSS, with unprovable enums marked INCONCLUSIVE |
439
- | `data/node-property-verification.csv` | 181 | **swept live** — each property probed with a value shaped by its own validator chain, on a type that declares it |
479
+ | `data/node-property-verification.csv` | 182 | **swept live** — each property probed with a value shaped by its own validator chain, on a type that declares it |
440
480
  | `data/style-verification.csv` | 98 | **swept live** — every style property written to a page and checked against the compiled CSS, with its group beside the result |
441
481
  | `data/rwd-verification.csv` | 731 | **checked live** - every `_t`/`_m` declaration vs the served stylesheet, with status per row |
442
482
  | `data/browser-verification.csv` | 3988 | **computed in Chromium** - declared vs `getComputedStyle` at three viewports, `not-comparable` labelled per row |
@@ -450,11 +490,12 @@ so the pattern is in the data, not just in this paragraph.
450
490
  | `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
491
  | `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
492
  | `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 |
493
+ | `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 |
453
494
  | `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 |
454
495
  | `data/node-type-notes.csv` | 8 | where a sweep outcome is true but misleading on its own, why. Surfaced by `mo.py type` |
455
496
  | `data/interaction-verification.csv` | 7 | **probed live** - interaction animation shapes, with negative controls and the stored row beside the payload |
456
497
  | `data/intro-verification.csv` | 8 + 15 | **sampled live** - the entrance sequence over fifteen timestamps on a monotonic clock, plus the eight assertions about it |
457
- | `data/element-classes.csv` | 151 | **live** — the built-in class metas; their IDs are what an `elementClass` record must use |
498
+ | `data/variants.csv` | 152 | **live** — the variant catalog (Mosaic's built-in element classes); their IDs are what a `variant` record must use, and `class_name` is what the element emits |
458
499
  | `data/dynamic-variables.csv` | 74 | source — every `@VAR('ns/name')` expression, by namespace |
459
500
  | `data/evaluator-functions.csv` | 19 | source — the `@` functions with their arity |
460
501
  | `data/interaction-types.csv` | 12 | source — trigger types, `timed` vs `progress` |
@@ -463,7 +504,7 @@ so the pattern is in the data, not just in this paragraph.
463
504
  | `data/condition-comparators.csv` | 12 | **live** — comparators and their operator sets |
464
505
  | `data/pluggables.csv` | 207 | source — every `setID()` by registry |
465
506
  | `data/rest-routes.csv` | 114 | **live** — method, path, args |
466
- | `data/db-columns.csv` | 206 | **live** — every column of all 23 tables |
507
+ | `data/db-columns.csv` | 210 | **live** — every column of all 23 tables |
467
508
 
468
509
  ## Building a page
469
510
 
@@ -490,19 +531,19 @@ tokens and element-class typography rather than per-node values:
490
531
  ```jsonc
491
532
  "theme": {
492
533
  "variables": {"--brand": {"type": "color", "value": "rgb(13,108,102)"}},
493
- "elementClasses": {"Heading 1": {"&": {"_": {"color": {"token": "--brand"}}}}}
534
+ "variants": {"Heading 1": {"&": {"_": {"color": {"token": "--brand"}}}}}
494
535
  }
495
536
  ```
496
537
 
497
538
  `{"token": "--brand"}` anywhere in a style resolves to the `{"var": "<uuid>"}`
498
539
  reference the compiler wants.
499
540
 
500
- **Two brands in one theme need namespaced tokens and no element classes at all.**
501
- Collection variables and element classes are both theme-global: two specs that each
541
+ **Two brands in one theme need namespaced tokens and no variants at all.**
542
+ Collection variables and variants are both theme-global: two specs that each
502
543
  declare `--ink` produce one `:root` with duplicate declarations, and two specs that
503
544
  each style `Heading 1` produce one set of rules. Whichever committed last wins, for
504
545
  every page. `sites/_moksa.py` namespaces its tokens (`--mk-*`) and bakes the type
505
- system onto the nodes with `apply_type()` instead of using element classes, which is
546
+ system onto the nodes with `apply_type()` instead of using variants, which is
506
547
  what lets it share an install with a completely different design.
507
548
 
508
549
  Whole themes move between installs with `theme_export.php` / `theme_import.php`.
@@ -552,8 +593,10 @@ post — `build_all.py` resets first for that reason.
552
593
 
553
594
  ## Facts worth knowing before you look anything up
554
595
 
555
- - **The REST namespace contains the plugin version** (`/wp-json/mosaic/v1.0.7`). Read
556
- it from `mosaicOptions.rest_api_url`, never hardcode.
596
+ - **The REST namespace contains the plugin version** (`/wp-json/mosaic/v1.0.8`). Read
597
+ it from `mosaicOptions.rest_api_url`, never hardcode. After a plugin update the
598
+ editor namespace is GONE until the data upgrade has run; only
599
+ `mosaic/<dataVersion>/<version>/upgrade` answers. `tools/data_upgrade.py`.
557
600
  - **`ordering` is a fractional-index string**, not a number.
558
601
  - **The tree is a `parentID` column**, not nesting. There is no page-level JSON blob.
559
602
  - **Everything is scoped to a `themeID`**, breakpoints included.
@@ -564,8 +607,13 @@ post — `build_all.py` resets first for that reason.
564
607
  - **Commit has side effects.** `heal()` runs inside it and creates breakpoints,
565
608
  collections and child nodes you never sent. Take every revision in the response.
566
609
  - **`modified_gmt` is server-assigned.**
567
- - **Styling hangs on the generated `.M_EL<n>` class, not your `attrID`**, and that
568
- number is not stable. The sibling `M_EL_<Type>` class is.
610
+ - **Styling hangs on the generated `._<token>` class, not your `attrID`**, and the
611
+ token (`_a`, `_b`, ... `_ba`: a base-38 per-document counter) is not stable. The
612
+ sibling `m-<type>` class is (`m-div`, `m-text m-wysiwyg`, `m-menu-link`), and a
613
+ variant that renders its name adds it (`m-heading-2`). Before 1.0.8 the same
614
+ three were `M_EL4`, `M_EL_Div` and `M_EL_Text__Heading2`; state classes moved
615
+ with them (`m-accordion-item--opened`, `m-menu-link--current`, `m-tab--active`).
616
+ Any CSS you write against an emitted class is coupled to the plugin version.
569
617
  - **`body{opacity:0}`** — the theme reveals itself from JavaScript. Screenshot tooling
570
618
  must let scripts run or it captures a blank page.
571
619
  - **Two themes can carry the same name, and only the ACTIVE one is bound to
@@ -580,7 +628,7 @@ post — `build_all.py` resets first for that reason.
580
628
  STALE CACHE when they differ by more than a percent - believe it, then find
581
629
  every layer. `wp breeze purge --cache=all` was the one that mattered.
582
630
  - **A `code` node is a wrapper, not a splice.** `insertLocation:"inPlace"` puts your
583
- markup INSIDE `<div class="M_EL_Code">`, one level down - so a sibling combinator
631
+ markup INSIDE `<div class="m-code _x">`, one level down - so a sibling combinator
584
632
  from injected HTML to a Mosaic node never matches, and a control you inject has
585
633
  to be styled through its own ancestors or through `:has()`. The native accordion
586
634
  avoided the question entirely.
@@ -596,10 +644,38 @@ post — `build_all.py` resets first for that reason.
596
644
  - **The front page 301s.** When a post is `page_on_front`, its own permalink
597
645
  (`/moksa/`) redirects to `/`; a fetch that does not follow redirects reads 0
598
646
  bytes and looks like an outage.
647
+ - **Every `build_site.py` run leaves a master behind, and they add up.** After a
648
+ season of probing the test theme held 223 masters and 98,261 nodes with 25
649
+ masters actually bound; Mosaic's ZIP import then spent 230s on masters and died
650
+ in templates when nginx closed the upstream at five minutes. Pruned to the bound
651
+ set (masters not reached from a bound template, their templates, their nodes)
652
+ the same import took under two minutes and compared 22 of 22. Prune before you
653
+ export; `wp_mosaic_template_assigns.parentID` -> template -> `masterID` is the
654
+ bound set, plus any `assign="auto"` template.
655
+ - **A custom field is `@VAR('post/meta_<key>')`, and a multi-value one is a LOOP.**
656
+ ACF and Meta Box both, plus bare post meta. Derived properties hang off the
657
+ name with two underscores (`meta_k__label`, `__url`, `__id`); an ACF group is
658
+ `loop-k` (hyphen) with `item/value_<sub>` rows; ACF puts the ID in a
659
+ reference's primary slot where Meta Box puts the title. Run
660
+ `tools/list_fields.php` on the post rather than guessing - a name it does not
661
+ print does not exist. references/custom-fields.md
662
+ - **A variant row cannot be deleted, only emptied.** `status:"delete"` on a
663
+ variant is accepted and ignored - the row is catalog-backed. Commit it back with
664
+ `{"states": {"&": {"_": {}}}}` and the rule disappears; measured on Heading 2.
665
+ - **User class names have a grammar.** Variant sub classes and universal classes
666
+ emit `[a-z0-9_-]` segments joined by `--` (`m-button--primary--sm`), each segment
667
+ at most 60 characters, never starting with `_`, a digit or the reserved `m-`;
668
+ names that do not sanitize fall back to `class`, and collisions get `-2`, `-3`.
669
+ The emitted name is UNIQUE per theme (an `emittedName` column with a unique
670
+ index on four tables), so two classes cannot share a spelling.
599
671
 
600
672
  ## Rendered-tag facts you would otherwise guess wrong
601
673
 
602
- - **`button` renders as `<span>`**, not `<button>`. So do `menu-link` and `wysiwyg-link`.
674
+ - **`button` renders three tags since 1.0.8, by two properties.** With a `url` it is
675
+ `<a href>`; without one it is `<button type="button">` (1.0.7 emitted `<span>`);
676
+ with `inactive:"1"` - the new integer-as-string property, the Badge variant's
677
+ default - it is a `<span>`, a label rather than a control. Measured all three.
678
+ `menu-link` and `wysiwyg-link` still render `<span>` without a `url`.
603
679
  - **`text` renders as `<div>` by default** — set `tagName` for `<h1>`, `<p>` and so on.
604
680
  - **Eight types emit custom elements**: `<mosaic-dropdown>`, `<mosaic-navbar>`,
605
681
  `<mosaic-tabs>`, `<mosaic-accordion>`, `<mosaic-vimeo>`, `<mosaic-youtube>` and
@@ -643,6 +719,8 @@ post — `build_all.py` resets first for that reason.
643
719
  | `theme_zip.py` | drive Mosaic's OWN export/import - the ZIP the editor makes, attachments included, over the milestone protocol; import lands in test mode unless told `--activate` |
644
720
  | `theme_zip_compare.php` | hold an imported copy against its source, tree for tree - every scoped table, the (parentType, type) shape, which ids survive, and orphans named rather than counted |
645
721
  | `theme_delete.php` | remove a theme completely through the plugin's own routine; refuses the live one |
722
+ | `list_fields.php` | every `@VAR` / `@LOOP` name Mosaic registers for one post - custom fields, their derived `__label` / `__url` / `__id` properties, the row variables of each loop - with the value each resolves to (`wp eval-file`) |
723
+ | `data_upgrade.py` | after a plugin update, run Mosaic's data migration over its own milestone route - the step wp-admin does from a screen - and set the config's version when the editor API is back |
646
724
  | `copy_styles.py` | push one node's style onto others, by attrID or prefix |
647
725
  | `bootstrap_probe_theme.php` | a licence-free scratch theme |
648
726
  | `mint_session.php` | a matching cookie + `wp_rest` nonce from WP-CLI |
@@ -657,7 +735,7 @@ python tools/extract_default_children.py <plugin-root> data/
657
735
  python tools/extract_style_properties.py <plugin-root> data/
658
736
  python tools/extract_interactions.py <plugin-root> data/
659
737
  python tools/extract_dynamic_variables.py <plugin-root> data/
660
- python tools/capture_live.py data/ # from data/raw/*.json
738
+ python tools/capture_live.py data/ # from data/raw/*.json + db-columns.txt
661
739
  # (raw dumps are gitignored;
662
740
  # re-capture from a live site)
663
741
 
@@ -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.2"
23
+ "version": "1.19.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.17.2"
23
+ "version": "1.19.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.17.2"
23
+ "version": "1.19.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.17.2"
23
+ "version": "1.19.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.17.2"
23
+ "version": "1.19.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,
@@ -77,12 +77,13 @@ const rows = (p) => read(p).trim().split("\n").length - 1; // minus the header
77
77
  const counts = {
78
78
  "data/node-verification.csv": 122,
79
79
  "data/style-verification.csv": 98,
80
- "data/node-property-verification.csv": 181,
80
+ "data/node-property-verification.csv": 182,
81
81
  "data/rwd-verification.csv": 731,
82
82
  "data/browser-verification.csv": 3988,
83
83
  "data/style-state-verification.csv": 52,
84
84
  "data/interaction-verification.csv": 7,
85
85
  "data/data-class-hierarchy.csv": 121,
86
+ "data/custom-fields-verification.csv": 62,
86
87
  };
87
88
  for (const [file, expected] of Object.entries(counts)) {
88
89
  if (!fs.existsSync(path.join(ROOT, file))) { fail(`${file} missing`); continue; }
@@ -1,6 +1,6 @@
1
1
  step,result,detail
2
2
  nested commit accepted,PASS,no exception from the commit
3
- page still renders,PASS,28853 bytes delivered
3
+ page still renders,PASS,28783 bytes delivered
4
4
  title rendered,PASS,the accordion title is in the delivered HTML
5
5
  content rendered,PASS,the accordion content is in the delivered HTML
6
6
  carries plugin markup,PASS,found 'mosaic-accordion' in the output