mosaic-headless 1.23.1 → 1.25.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.
package/SKILL.md CHANGED
@@ -4,7 +4,7 @@ description: |
4
4
  Build and modify Mosaic Pro (Nextend) sites by writing the underlying data model directly - no visual editor, no DOM. Query the real surface with `mo.py`, which joins every source table to the live sweeps so a lookup leads with the measured verdict rather than the declaration (126 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
6
  author: "moksa (https://moksaweb.com)"
7
- version: "1.23.1"
7
+ version: "1.25.0"
8
8
  ---
9
9
 
10
10
  # Headless Mosaic
@@ -529,11 +529,13 @@ so the pattern is in the data, not just in this paragraph.
529
529
  | `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 |
530
530
  | `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 |
531
531
  | `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 |
532
- | `data/slider-verification.csv` | 19 | **driven live** - two sliders, one autoplaying and one a carousel: advance, arrows, bullets, keyboard, reduced motion |
533
- | `data/navigation-verification.csv` | 46 | **fetched and opened live** - every menu item is a real `<a href>`, every page of the nineteen answers 200, and no page scrolls sideways at 390 / 768 / 1280 |
532
+ | `data/slider-verification.csv` | 30 | **driven live** - two sliders, one autoplaying and one a carousel: advance, arrows, bullets, keyboard, reduced motion |
533
+ | `data/navigation-verification.csv` | 27 | **fetched and opened live** - every menu item is a real `<a href>`, every page of the nineteen answers 200, and no page scrolls sideways at 390 / 768 / 1280 |
534
534
  | `data/page-modal-verification.csv` | 19 | **read live** - one modal per page, and what each one's trigger, run rule, memory scope and dismissal policy actually came out as |
535
535
  | `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
536
  | `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
+ | `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` | 54 | **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 |
537
539
  | `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 |
538
540
  | `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 |
539
541
  | `data/node-type-notes.csv` | 8 | where a sweep outcome is true but misleading on its own, why. Surfaced by `mo.py type` |
@@ -805,10 +807,12 @@ post — `build_all.py` resets first for that reason.
805
807
  | `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` |
806
808
  | `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 |
807
809
  | `theme_delete.php` | remove a theme completely through the plugin's own routine; refuses the live one |
808
- | `verify_slider.py` | the slider in a real browser: does it advance, do the arrows and bullets drive it, does the keyboard, does it stop when motion is not wanted |
810
+ | `verify_slider.py` | the slider in a real browser: does it advance, do the arrows and bullets drive it, does the keyboard, does it stop when motion is not wanted - `--label`/`--append` put several sliders in one table, which is where a carousel and a plain slider can be held against each other |
809
811
  | `verify_navigation.py` | a multi-page site: is every menu item a real `<a href>`, does every page answer 200 (a template that was never bound answers 406 with an empty body, which only a logged-out visitor sees), and with `--viewports` does any page scroll SIDEWAYS - the one question no per-page checker asks |
810
812
  | `verify_dialog.py` | the 1.0.9 modal in a real browser: does it open, close, answer Esc, remember, cap - and does an OpenStreetMap carry the coordinates it was given |
811
813
  | `verify_dialog_lab.py` | the question after "does the modal work": can three nodes be made into a bottom sheet, a side drawer, a corner notice, a full takeover and a gate with no way out - and does each one still fit at phone width, which the page's own width check cannot see |
814
+ | `verify_tokens.py` | the design tokens as the BROWSER resolved them: every `:root` property declared once (a changed value does not retire the old one), non-empty, and actually pointed at by something - a wrong `skinsData` shape still emits the declaration and serves white |
815
+ | `audit_mobile.py` | the RWD defects a sideways-scroll check cannot see - type under the legible floor, controls too small to hit, a grid that never collapsed, content escaping a box that is not clipping it - re-opened at every `--widths` |
812
816
  | `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`) |
813
817
  | `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 |
814
818
  | `copy_styles.py` | push one node's style onto others, by attrID or prefix |
@@ -822,7 +826,7 @@ post — `build_all.py` resets first for that reason.
822
826
  python tools/extract_node_types.py <plugin-root> data/
823
827
  python tools/extract_pluggables.py <plugin-root> data/
824
828
  python tools/extract_placement.py <plugin-root> data/
825
- python tools/extract_default_children.py <plugin-root> data/
829
+ python tools/extract_default_children.py <plugin-root> data/ # now also records heal-inserted children
826
830
  python tools/extract_style_properties.py <plugin-root> data/
827
831
  python tools/extract_interactions.py <plugin-root> data/
828
832
  python tools/extract_dynamic_variables.py <plugin-root> data/
@@ -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.23.1"
23
+ "version": "1.25.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.23.1"
23
+ "version": "1.25.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.23.1"
23
+ "version": "1.25.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.23.1"
23
+ "version": "1.25.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.23.1"
23
+ "version": "1.25.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,
@@ -1,11 +1,26 @@
1
- type,default_children,child_count,declared_in
2
- accordion-content,text|wysiwyg-text,2,Mosaic/NodeTypes/Accordion/AccordionItem/AccordionContent/AccordionContentElementTypeFactory.php
3
- accordion-item,accordion-title|accordion-content,2,Mosaic/NodeTypes/Accordion/AccordionItem/AccordionItemElementTypeFactory.php
4
- slider-navigation,slider-navigation-bullet,1,Mosaic/NodeTypes/Pro/Slider/SliderNavigation/SliderNavigationElementTypeFactory.php
5
- tabs-content,tabs-tab-pane,1,Mosaic/NodeTypes/Pro/Tabs/TabsContent/TabsContentElementTypeFactory.php
6
- tabs-loop-tab-panes,text|wysiwyg-variable,2,Mosaic/NodeTypes/Pro/Tabs/TabsContent/LoopTabPanes/TabLoopTabPanesElementTypeFactory.php
7
- tabs-loop-tabs,text|wysiwyg-variable,2,Mosaic/NodeTypes/Pro/Tabs/TabsMenu/LoopTabs/TabLoopTabsElementTypeFactory.php
8
- tabs-menu,tabs-tab,1,Mosaic/NodeTypes/Pro/Tabs/TabsMenu/TabsMenuElementTypeFactory.php
9
- tabs-tab,text|wysiwyg-text,2,Mosaic/NodeTypes/Pro/Tabs/TabsMenu/Tab/TabElementTypeFactory.php
10
- tabs-tab-pane,text|wysiwyg-text,2,Mosaic/NodeTypes/Pro/Tabs/TabsContent/TabPane/TabPaneElementTypeFactory.php
11
- text,wysiwyg-text,1,Mosaic/NodeTypes/Text/TextElementTypeFactory.php
1
+ type,default_children,child_count,source,declared_in
2
+ accordion-content,text|wysiwyg-text,2,default,Mosaic/NodeTypes/Accordion/AccordionItem/AccordionContent/AccordionContentElementTypeFactory.php
3
+ accordion-item,accordion-title|accordion-content,2,default,Mosaic/NodeTypes/Accordion/AccordionItem/AccordionItemElementTypeFactory.php
4
+ choice-field,choice-legend|choice-content,2,heal,Mosaic/NodeTypes/Pro/Form/Form/MElements/ChoiceField/ChoiceFieldElementMResource.php
5
+ dropdown,dropdown-toggle|dropdown-wrapper,2,heal,Mosaic/NodeTypes/Menu/Dropdown/Dropdown/DropdownElementMResource.php
6
+ dropdown-wide,dropdown-toggle|dropdown-wrapper-wide,2,heal,Mosaic/NodeTypes/Menu/Dropdown/DropdownWide/DropdownWideElementMResource.php
7
+ form-multi-steps,form-multi-steps-steps|multi-steps-forms|form-multi-steps-step|multi-steps-form-step,4,heal,Mosaic/NodeTypes/Pro/Form/Form/MElements/MultiSteps/MultiStepsElementMResource.php
8
+ form-wrapper,form|success-screen,2,heal,Mosaic/NodeTypes/Pro/Form/FormWrapperElementMResource.php
9
+ loop,loop-items|loop-no-result,2,heal,Mosaic/NodeTypes/Loop/LoopElementMResource.php
10
+ loop-pagination-numbers,loop-pagination-number,1,heal,Mosaic/NodeTypes/Loop/LoopPagination/Numbers/LoopPaginationNumbersElementMResource.php
11
+ modal,modal-overlay,1,heal,Mosaic/NodeTypes/Dialog/Modal/ModalElementMResource.php
12
+ slider,slider-slides,1,heal,Mosaic/NodeTypes/Pro/Slider/SliderElementMResource.php
13
+ slider-navigation,slider-navigation-bullet,1,default,Mosaic/NodeTypes/Pro/Slider/SliderNavigation/SliderNavigationElementTypeFactory.php
14
+ slider-navigation,slider-navigation-bullet,1,heal,Mosaic/NodeTypes/Pro/Slider/SliderNavigation/SliderNavigationElementMResource.php
15
+ styleguide-entry-content-image,styleguide-entry-content-image-img|styleguide-entry-content-image-caption,2,heal,Mosaic/NodeTypes/SG/SGEntryContent/SGEntryContentImage/SGEntryContentImageElementMResource.php
16
+ styleguide-entry-content-list,styleguide-entry-content-list-item,1,heal,Mosaic/NodeTypes/SG/SGEntryContent/SGEntryContentList/SGEntryContentListElementMResource.php
17
+ styleguide-entry-content-pullquote,styleguide-entry-content-pullquote-paragraph|styleguide-entry-content-pullquote-cite,2,heal,Mosaic/NodeTypes/SG/SGEntryContent/SGEntryContentPullQuote/SGEntryContentPullQuoteElementMResource.php
18
+ styleguide-entry-content-text-quote,styleguide-entry-content-quote-cite,1,heal,Mosaic/NodeTypes/SG/SGEntryContent/SGEntryContentQuote/SGEntryContentQuoteElementMResource.php
19
+ submit-button,submit-label|submit-loading,2,heal,Mosaic/NodeTypes/Pro/Form/Form/MElements/SubmitButton/SubmitButtonElementMResource.php
20
+ tabs-content,tabs-tab-pane,1,default,Mosaic/NodeTypes/Pro/Tabs/TabsContent/TabsContentElementTypeFactory.php
21
+ tabs-loop-tab-panes,text|wysiwyg-variable,2,default,Mosaic/NodeTypes/Pro/Tabs/TabsContent/LoopTabPanes/TabLoopTabPanesElementTypeFactory.php
22
+ tabs-loop-tabs,text|wysiwyg-variable,2,default,Mosaic/NodeTypes/Pro/Tabs/TabsMenu/LoopTabs/TabLoopTabsElementTypeFactory.php
23
+ tabs-menu,tabs-tab,1,default,Mosaic/NodeTypes/Pro/Tabs/TabsMenu/TabsMenuElementTypeFactory.php
24
+ tabs-tab,text|wysiwyg-text,2,default,Mosaic/NodeTypes/Pro/Tabs/TabsMenu/Tab/TabElementTypeFactory.php
25
+ tabs-tab-pane,text|wysiwyg-text,2,default,Mosaic/NodeTypes/Pro/Tabs/TabsContent/TabPane/TabPaneElementTypeFactory.php
26
+ text,wysiwyg-text,1,default,Mosaic/NodeTypes/Text/TextElementTypeFactory.php
@@ -31,9 +31,9 @@ GATE/button-closes,PASS,the modalClose inside is the only exit
31
31
  DEPTH/shut-at-rest,PASS,
32
32
  DEPTH/opens-past-75,PASS,scrollDepth threshold 75%
33
33
  EXIT/opens,PASS,exitIntent is desktop-only and synthetic leave is approximate
34
- LATCH/record-abb,PASS,"{""counter"":1,""firstAt"":1791092432503,""lastAt"":1791092432503,""lastID"":""a00fdaff-b"
34
+ LATCH/record-abb,PASS,"{""counter"":1,""firstAt"":1791103196338,""lastAt"":1791103196338,""lastID"":""75cb0b1a-7"
35
35
  LATCH/B-first-wins,PASS,"6/6 as predicted, 0 lost clicks {'abb>abb,aba>none': 6}"
36
- LATCH/record-aba,PASS,"{""counter"":1,""firstAt"":1791092481637,""lastAt"":1791092481637,""lastID"":""d3745edf-4"
36
+ LATCH/record-aba,PASS,"{""counter"":1,""firstAt"":1791103249401,""lastAt"":1791103249401,""lastID"":""fbc055d4-7"
37
37
  LATCH/A-first-wins,PASS,"6/6 as predicted, 0 lost clicks {'aba>aba,abb>none': 6}"
38
38
  LATCH/order-decides,PASS,"the winner flips with click order, so it is the first EVALUATED, not the first authored"
39
39
  FIT390/centre,PASS,scrollW=349/349 childOverflow=0 pageScrollsSideways=False
@@ -0,0 +1,55 @@
1
+ path,width,finding,element,detail
2
+ dialogs/,390,GRID_NOT_COLLAPSED,mk-f-links,2 columns at 390px
3
+ sliders/,390,SMALL_TARGET,sl-main-nav-dot,26x28:
4
+ sliders/,390,SMALL_TARGET,sl-main-nav-dot,26x28:
5
+ sliders/,390,SMALL_TARGET,sl-main-nav-dot,26x28:
6
+ sliders/,390,SMALL_TARGET,sl-main-nav-dot,26x28:
7
+ sliders/,390,SMALL_TARGET,sl-carousel-nav-dot,26x28:
8
+ sliders/,390,SMALL_TARGET,sl-carousel-nav-dot,26x28:
9
+ sliders/,390,SMALL_TARGET,sl-carousel-nav-dot,26x28:
10
+ sliders/,390,SMALL_TARGET,sl-carousel-nav-dot,26x28:
11
+ sliders/,390,SMALL_TARGET,sl-carousel-nav-dot,26x28:
12
+ sliders/,390,SMALL_TARGET,sl-carousel-nav-dot,26x28:
13
+ sliders/,390,GRID_NOT_COLLAPSED,mk-f-links,2 columns at 390px
14
+ tokens/,390,GRID_NOT_COLLAPSED,mk-f-links,2 columns at 390px
15
+ dynamic/,390,GRID_NOT_COLLAPSED,mk-f-links,2 columns at 390px
16
+ conversion/,390,GRID_NOT_COLLAPSED,mk-f-links,2 columns at 390px
17
+ dialogs/,768,LONG_MEASURE,m-text,46 chars/line
18
+ dialogs/,768,LONG_MEASURE,m-text,45 chars/line
19
+ dialogs/,768,LONG_MEASURE,m-text,45 chars/line
20
+ sliders/,768,SMALL_TARGET,sl-main-nav-dot,26x28:
21
+ sliders/,768,SMALL_TARGET,sl-main-nav-dot,26x28:
22
+ sliders/,768,SMALL_TARGET,sl-main-nav-dot,26x28:
23
+ sliders/,768,SMALL_TARGET,sl-main-nav-dot,26x28:
24
+ sliders/,768,LONG_MEASURE,m-text,44 chars/line
25
+ sliders/,768,LONG_MEASURE,m-text,44 chars/line
26
+ sliders/,768,SMALL_TARGET,sl-carousel-nav-dot,26x28:
27
+ sliders/,768,SMALL_TARGET,sl-carousel-nav-dot,26x28:
28
+ sliders/,768,SMALL_TARGET,sl-carousel-nav-dot,26x28:
29
+ sliders/,768,SMALL_TARGET,sl-carousel-nav-dot,26x28:
30
+ sliders/,768,SMALL_TARGET,sl-carousel-nav-dot,26x28:
31
+ sliders/,768,SMALL_TARGET,sl-carousel-nav-dot,26x28:
32
+ tokens/,768,LONG_MEASURE,m-text,47 chars/line
33
+ tokens/,768,LONG_MEASURE,m-text,55 chars/line
34
+ tokens/,768,LONG_MEASURE,m-text,55 chars/line
35
+ tokens/,768,LONG_MEASURE,m-text,44 chars/line
36
+ tokens/,768,LONG_MEASURE,m-text,45 chars/line
37
+ tokens/,768,LONG_MEASURE,m-text,51 chars/line
38
+ tokens/,768,LONG_MEASURE,m-text,49 chars/line
39
+ tokens/,768,LONG_MEASURE,m-text,55 chars/line
40
+ tokens/,768,LONG_MEASURE,m-text,55 chars/line
41
+ tokens/,768,LONG_MEASURE,m-text,55 chars/line
42
+ dynamic/,768,LONG_MEASURE,m-text,43 chars/line
43
+ dynamic/,768,LONG_MEASURE,m-text,57 chars/line
44
+ dynamic/,768,LONG_MEASURE,m-text,49 chars/line
45
+ dynamic/,768,LONG_MEASURE,m-text,55 chars/line
46
+ dynamic/,768,LONG_MEASURE,m-text,55 chars/line
47
+ dynamic/,768,LONG_MEASURE,m-text,55 chars/line
48
+ dynamic/,768,LONG_MEASURE,m-text,55 chars/line
49
+ conversion/,768,LONG_MEASURE,m-text,46 chars/line
50
+ conversion/,768,LONG_MEASURE,m-text,47 chars/line
51
+ conversion/,768,LONG_MEASURE,m-text,45 chars/line
52
+ conversion/,768,LONG_MEASURE,m-text,49 chars/line
53
+ conversion/,768,LONG_MEASURE,m-text,55 chars/line
54
+ conversion/,768,LONG_MEASURE,m-text,55 chars/line
55
+ conversion/,768,LONG_MEASURE,m-text,55 chars/line
@@ -1,47 +1,28 @@
1
1
  check,result,detail
2
- FRONT_PAGE,PASS,"HTTP 200, 112365 bytes"
3
- MENU_IS_LINKS,PASS,6 real <a href> in #mw-nav
4
- NAV 首頁,PASS,"// -> HTTP 200, 112360 bytes"
5
- NAV 服務,PASS,"/services/ -> HTTP 200, 156950 bytes"
6
- NAV 作品,PASS,"/works/ -> HTTP 200, 109070 bytes"
7
- NAV 報價,PASS,"/plan/ -> HTTP 200, 181775 bytes"
8
- NAV 關於,PASS,"/about/ -> HTTP 200, 66293 bytes"
9
- NAV 聯絡,PASS,"/contact/ -> HTTP 200, 49689 bytes"
10
- PAGE home,PASS,"HTTP 200, 112741 bytes"
11
- PAGE services,PASS,"HTTP 200, 156570 bytes"
12
- PAGE mowp,PASS,"HTTP 200, 103205 bytes"
13
- PAGE weebly-migration,PASS,"HTTP 200, 96324 bytes"
14
- PAGE works,PASS,"HTTP 200, 109452 bytes"
15
- PAGE plan,PASS,"HTTP 200, 182156 bytes"
16
- PAGE about,PASS,"HTTP 200, 65912 bytes"
17
- PAGE teams,PASS,"HTTP 200, 68157 bytes"
18
- PAGE open-source,PASS,"HTTP 200, 115144 bytes"
19
- PAGE order-notify-guide,PASS,"HTTP 200, 40027 bytes"
20
- PAGE contact,PASS,"HTTP 200, 49689 bytes"
21
- PAGE case-drpen,PASS,"HTTP 200, 89163 bytes"
22
- PAGE case-lyycamp,PASS,"HTTP 200, 88098 bytes"
23
- PAGE case-em925,PASS,"HTTP 200, 86740 bytes"
24
- PAGE case-sweetluck,PASS,"HTTP 200, 87603 bytes"
25
- PAGE case-amclinic,PASS,"HTTP 200, 86880 bytes"
26
- PAGE case-ruinart,PASS,"HTTP 200, 87120 bytes"
27
- PAGE case-medical-erp,PASS,"HTTP 200, 88285 bytes"
28
- PAGE case-igoods,PASS,"HTTP 200, 86334 bytes"
29
- WIDTH home,PASS,"no sideways scroll at 390,768,1280"
30
- WIDTH services,PASS,"no sideways scroll at 390,768,1280"
31
- WIDTH mowp,PASS,"no sideways scroll at 390,768,1280"
32
- WIDTH weebly-migration,PASS,"no sideways scroll at 390,768,1280"
33
- WIDTH works,PASS,"no sideways scroll at 390,768,1280"
34
- WIDTH plan,PASS,"no sideways scroll at 390,768,1280"
35
- WIDTH about,PASS,"no sideways scroll at 390,768,1280"
36
- WIDTH teams,PASS,"no sideways scroll at 390,768,1280"
37
- WIDTH open-source,PASS,"no sideways scroll at 390,768,1280"
38
- WIDTH order-notify-guide,PASS,"no sideways scroll at 390,768,1280"
39
- WIDTH contact,PASS,"no sideways scroll at 390,768,1280"
40
- WIDTH case-drpen,PASS,"no sideways scroll at 390,768,1280"
41
- WIDTH case-lyycamp,PASS,"no sideways scroll at 390,768,1280"
42
- WIDTH case-em925,PASS,"no sideways scroll at 390,768,1280"
43
- WIDTH case-sweetluck,PASS,"no sideways scroll at 390,768,1280"
44
- WIDTH case-amclinic,PASS,"no sideways scroll at 390,768,1280"
45
- WIDTH case-ruinart,PASS,"no sideways scroll at 390,768,1280"
46
- WIDTH case-medical-erp,PASS,"no sideways scroll at 390,768,1280"
47
- WIDTH case-igoods,PASS,"no sideways scroll at 390,768,1280"
2
+ FRONT_PAGE,PASS,"HTTP 200, 236218 bytes"
3
+ MENU_IS_LINKS,PASS,9 real <a href> in #mk-nav
4
+ NAV 服務,PASS,"#services -> HTTP 200, 236315 bytes"
5
+ NAV 作品,PASS,"#works -> HTTP 200, 236315 bytes"
6
+ NAV 產品,PASS,"#products -> HTTP 200, 236315 bytes"
7
+ NAV 彈窗,PASS,"/dialogs/ -> HTTP 200, 202723 bytes"
8
+ NAV 輪播,PASS,"/sliders/ -> HTTP 200, 132373 bytes"
9
+ NAV 系統,PASS,"/tokens/ -> HTTP 200, 121169 bytes"
10
+ NAV 資料,PASS,"/dynamic/ -> HTTP 200, 107999 bytes"
11
+ NAV 轉換,PASS,"/conversion/ -> HTTP 200, 98579 bytes"
12
+ NAV 聯絡,PASS,"#contact -> HTTP 200, 236315 bytes"
13
+ PAGE moksa,PASS,"HTTP 200, 236218 bytes"
14
+ PAGE my-account,PASS,"HTTP 200, 96416 bytes"
15
+ PAGE intro,PASS,"HTTP 200, 130132 bytes"
16
+ PAGE conversion,PASS,"HTTP 200, 98200 bytes"
17
+ PAGE dynamic,PASS,"HTTP 200, 108379 bytes"
18
+ PAGE tokens,PASS,"HTTP 200, 120790 bytes"
19
+ PAGE sliders,PASS,"HTTP 200, 132753 bytes"
20
+ PAGE dialogs,PASS,"HTTP 200, 202724 bytes"
21
+ WIDTH moksa,PASS,"no sideways scroll at 390,768,1280"
22
+ WIDTH my-account,PASS,"no sideways scroll at 390,768,1280"
23
+ WIDTH intro,PASS,"no sideways scroll at 390,768,1280"
24
+ WIDTH conversion,PASS,"no sideways scroll at 390,768,1280"
25
+ WIDTH dynamic,PASS,"no sideways scroll at 390,768,1280"
26
+ WIDTH tokens,PASS,"no sideways scroll at 390,768,1280"
27
+ WIDTH sliders,PASS,"no sideways scroll at 390,768,1280"
28
+ WIDTH dialogs,PASS,"no sideways scroll at 390,768,1280"
@@ -1,20 +1,31 @@
1
1
  slider,check,result,detail
2
- slide+autoplay,HOST,PASS,"<mosaic-slider> animation=slide carousel=None, 3 slides"
3
- slide+autoplay,TRACK,PASS,<mosaic-slider-slides> aria-live=off
4
- slide+autoplay,HAS_CURRENT,PASS,slide 0 is current at rest
5
- slide+autoplay,BULLET_TEMPLATE,PASS,"3 bullets for 3 slides, all with id='sl-dot-0' - one authored bullet is repeated per slide, duplicate ids included"
6
- slide+autoplay,BULLET_ARIA,PASS,"role=button tabindex=0 labels=ONE / 浮世繪, TWO / 版面, THREE / 色票 - taken from each slide's own title"
7
- slide+autoplay,ADVANCES,PASS,reached 3 of 3 slides unaided
8
- slide+autoplay,AUTOPLAY_DECLARED,PASS,data-autoplay=true delay=2200
9
- slide+autoplay,BULLET_GOES,PASS,bullet 0 -> slide 0
10
- slide+autoplay,ARROW_ENDS,PASS,"on slide 0 prev hides itself (hidden=True), next does not (hidden=False)"
11
- slide+autoplay,ARROW_ARIA,PASS,role=button tabindex=0 label='下一張投影片'
12
- slide+autoplay,NEXT,PASS,slide 0 -> 1
13
- slide+autoplay,PREV,PASS,slide 1 -> 0
14
- slide+autoplay,KEYBOARD,PASS,"focus bullet 2 taken, Enter -> slide 2"
15
- slide+autoplay,REDUCED,PASS,under prefers-reduced-motion it holds still (slide 0 -> 0)
16
- crossFade+carousel,HOST,PASS,"<mosaic-slider> animation=crossFade carousel=true, 4 slides"
17
- crossFade+carousel,TRACK,PASS,<mosaic-slider-slides> aria-live=polite
18
- crossFade+carousel,HAS_CURRENT,PASS,slide 0 is current at rest
19
- crossFade+carousel,STAYS_PUT,PASS,autoplay off: still on slide 0 after 2.6s
20
- crossFade+carousel,REDUCED,PASS,under prefers-reduced-motion it holds still (slide 0 -> 0)
2
+ "hero, 4 slides, plain",HOST,PASS,"<mosaic-slider> animation=slide carousel=None, 4 slides"
3
+ "hero, 4 slides, plain",TRACK,PASS,<mosaic-slider-slides> aria-live=off
4
+ "hero, 4 slides, plain",HAS_CURRENT,PASS,slide 0 is current at rest
5
+ "hero, 4 slides, plain",BULLET_TEMPLATE,PASS,"4 bullets for 4 slides, all with id='sl-main-nav-dot' - one authored bullet is repeated per slide, duplicate ids included"
6
+ "hero, 4 slides, plain",BULLET_ARIA,PASS,"role=button tabindex=0 labels=一、輪播不是相簿, 二、四種動畫, 三、箭頭會自己消失, 四、不想動就不動 - taken from each slide's own title"
7
+ "hero, 4 slides, plain",ADVANCES,PASS,reached 4 of 4 slides unaided
8
+ "hero, 4 slides, plain",AUTOPLAY_DECLARED,PASS,data-autoplay=true delay=3600
9
+ "hero, 4 slides, plain",BULLET_GOES,PASS,bullet 0 -> slide 0
10
+ "hero, 4 slides, plain",ARROW_ENDS,PASS,"on slide 0 prev hides itself (hidden=True), next does not (hidden=False)"
11
+ "hero, 4 slides, plain",TRACK_LAYOUT,PASS,"grid ""slide"", every slide one track wide ([1200] vs 1200)"
12
+ "hero, 4 slides, plain",ARROW_ARIA,PASS,role=button tabindex=0 label='下一張投影片'
13
+ "hero, 4 slides, plain",NEXT,PASS,slide 0 -> 1
14
+ "hero, 4 slides, plain",PREV,PASS,slide 1 -> 0
15
+ "hero, 4 slides, plain",KEYBOARD,PASS,"focus bullet 3 taken, Enter -> slide 3"
16
+ "hero, 4 slides, plain",REDUCED,PASS,under prefers-reduced-motion it holds still (slide 0 -> 0)
17
+ "carousel, 6 slides, wraps",HOST,PASS,"<mosaic-slider> animation=slide carousel=true, 6 slides"
18
+ "carousel, 6 slides, wraps",TRACK,PASS,<mosaic-slider-slides> aria-live=off
19
+ "carousel, 6 slides, wraps",HAS_CURRENT,PASS,slide 0 is current at rest
20
+ "carousel, 6 slides, wraps",BULLET_TEMPLATE,PASS,"6 bullets for 6 slides, all with id='sl-carousel-nav-dot' - one authored bullet is repeated per slide, duplicate ids included"
21
+ "carousel, 6 slides, wraps",BULLET_ARIA,PASS,"role=button tabindex=0 labels=WEB, AI, ERP, SEO, SHOP, AUTOMATION - taken from each slide's own title"
22
+ "carousel, 6 slides, wraps",ADVANCES,PASS,reached 6 of 6 slides unaided
23
+ "carousel, 6 slides, wraps",AUTOPLAY_DECLARED,PASS,data-autoplay=true delay=2400
24
+ "carousel, 6 slides, wraps",BULLET_GOES,PASS,bullet 0 -> slide 0
25
+ "carousel, 6 slides, wraps",ARROW_ENDS,PASS,"carousel: it wraps, so neither arrow hides (prev hidden=False, next hidden=False)"
26
+ "carousel, 6 slides, wraps",CAROUSEL_LAYOUT,PASS,"grid ""slide"", every slide still one track wide ([1200] vs 1200) - isCarousel wraps the ends, it does not show more than one"
27
+ "carousel, 6 slides, wraps",ARROW_ARIA,PASS,role=button tabindex=0 label='下一張投影片'
28
+ "carousel, 6 slides, wraps",NEXT,PASS,slide 0 -> 1
29
+ "carousel, 6 slides, wraps",PREV,PASS,slide 1 -> 0
30
+ "carousel, 6 slides, wraps",KEYBOARD,PASS,"focus bullet 5 taken, Enter -> slide 5"
31
+ "carousel, 6 slides, wraps",REDUCED,PASS,under prefers-reduced-motion it holds still (slide 0 -> 0)
@@ -0,0 +1,16 @@
1
+ check,result,detail
2
+ ROOT_PRESENT,PASS,9 custom properties on :root matching '--mk-'
3
+ NO_DUPLICATES,PASS,every property declared once
4
+ NO_EMPTY,PASS,every token has a value
5
+ NOT_DEFAULT_WHITE,PASS,none
6
+ ALL_COLOUR_TOKENS_USED,PASS,all 9 colour tokens are resolved by at least one element
7
+ NON_COLOUR_TOKENS,PASS,"3 not judged by colour (they are numbers or lengths): --mk-m=0, --mk-n=0, --mk-s=0"
8
+ USED/--mk-accent,PASS,"5 element(s), e.g. mk-progress-track, mk-progress, mk-clock-dot"
9
+ USED/--mk-accent-ink,PASS,"2 element(s), e.g. mk-header-cta, tk-chip-mk-accent-ink"
10
+ USED/--mk-ai,PASS,"1 element(s), e.g. tk-chip-mk-ai"
11
+ USED/--mk-faint,PASS,"1 element(s), e.g. tk-chip-mk-faint"
12
+ USED/--mk-ink,PASS,"76 element(s), e.g. mk-clock-m, mk-clock-m, mk-clock-s"
13
+ USED/--mk-muted,PASS,"1 element(s), e.g. tk-chip-mk-muted"
14
+ USED/--mk-panel,PASS,"3 element(s), e.g. tk-sw, tk-chip-mk-panel, tk-props"
15
+ USED/--mk-paper,PASS,"7 element(s), e.g. tk-head, tk-chip-mk-paper, tk-pipe"
16
+ USED/--mk-rule,PASS,"1 element(s), e.g. tk-chip-mk-rule"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mosaic-headless",
3
- "version": "1.23.1",
3
+ "version": "1.25.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",
@@ -172,3 +172,56 @@ scores its own blind spots as successes is worse than no tool.
172
172
  responsive behaviour you want checked.
173
173
 
174
174
  The tool tells you the values arrived. Only the browser tells you they add up.
175
+
176
+ ## Five ways a page passes the width check and is still broken on a phone
177
+
178
+ `verify_navigation.py --viewports` asks one question — does the document scroll
179
+ sideways — and a page can pass it while being unusable. `tools/audit_mobile.py`
180
+ asks the rest, re-opening each page at every width and reporting WIDE, OUTSIDE,
181
+ CLIPPED, TINY_TYPE, SMALL_TARGET, GRID_NOT_COLLAPSED and LONG_MEASURE. Run on the
182
+ five showcase pages it found all five of the following, none of which the width
183
+ check could see.
184
+
185
+ **`flexSizing: {"type": "grow"}` is `flex-grow:1; flex-shrink:0`.** It is not
186
+ `flex: 1`. A value column set with it can grow but can never give ground, so one
187
+ long mono string in a 310px card pushed the whole document out to 440px — and the
188
+ page still "did not scroll sideways", because the document itself had simply become
189
+ wider. The four types compile as:
190
+
191
+ | type | emitted |
192
+ |---|---|
193
+ | `none` | `flex-grow:0; flex-shrink:0` |
194
+ | `shrink` | `flex-grow:0; flex-shrink:1` |
195
+ | `grow` | `flex-grow:1; flex-shrink:0` |
196
+ | `custom` | whichever of `flexGrow` / `flexShrink` you set |
197
+
198
+ So an item that must both fill and yield needs `custom` with both set to `1`.
199
+
200
+ **A grid or flex item defaults to `min-width: auto`, which means min-content.** A
201
+ code block in `white-space: pre` has a min-content width of its longest LINE, so
202
+ the track refuses to shrink and the grid overflows its container. `overflow: auto`
203
+ on the block does not help — the item ABOVE it is what is too wide. `minWidth: 0`
204
+ on the item is the fix.
205
+
206
+ **`section` arrives with 80px of vertical padding.** It is invisible until a page
207
+ adds its own spacing, and then every band carries 80 + 80 + yours; the showcase
208
+ pages opened with about 200px of nothing. `section()` in the demo now takes a
209
+ `pad_y` so a page can take that rhythm into its own hands.
210
+
211
+ **A fixed header's height is not a constant.** The demo's nav grew from four items
212
+ to nine as pages were added; on a phone it wrapped to two rows and took the fixed
213
+ header to 177px, with the first 97px of every page hidden underneath it, because
214
+ the pages cleared a height decided when it was one line. Below 768px the header
215
+ simply joins the flow now, which removes the whole class of bug.
216
+
217
+ **A type scale needs a floor, not just a ramp.** 10px mono labels are part of the
218
+ hierarchy on a large screen and illegible on a phone; the audit found 80 runs of
219
+ them on one page. The demo's `mono()` raises anything under 12px at `_t` — which
220
+ is `<=1079px`, so it covers the tablet too, where a 768px touch device was still
221
+ being served desktop values.
222
+
223
+ `data/mobile-audit.csv` is the standing result. Three classes in it are accepted
224
+ rather than fixed, and are worth stating so they are not re-opened: the footer link
225
+ list is deliberately two columns, the slider bullets are 26x28 (a secondary control
226
+ below the 30px the tool asks for), and LONG_MEASURE flags CJK lines over 42
227
+ characters, which at 768px is a preference rather than a defect.
@@ -5,7 +5,7 @@
5
5
  about the component — the same kind of row the accordion probe corrected. Nested
6
6
  the way its factories require, every piece commits and renders.
7
7
 
8
- `data/slider-verification.csv`: 19 checks across two sliders, all passing.
8
+ `data/slider-verification.csv`: 30 checks across two sliders, all passing.
9
9
 
10
10
  ## The shape
11
11
 
@@ -28,8 +28,24 @@ the way its factories require, every piece commits and renders.
28
28
  ```
29
29
 
30
30
  `animation` is `slide` | `crossFade` | `fadeOut` | `fadeOver`. `autoplayLimit` is how
31
- many rounds autoplay runs before it stops, `0` meaning no limit. `isCarousel` shows
32
- more than one slide at a time *and* is what lets the ends wrap.
31
+ many rounds autoplay runs before it stops, `0` meaning no limit.
32
+
33
+ **`isCarousel` makes the ends wrap. It does not show more than one slide at a
34
+ time** — an earlier version of this page claimed both, and measuring the two side
35
+ by side says otherwise. `<mosaic-slider-slides>` is a CSS grid whose template is a
36
+ single `"slide"` area one track wide, and *every* slide sits in that one area at
37
+ the full track width, carousel or not:
38
+
39
+ | | plain | carousel |
40
+ |---|---|---|
41
+ | `grid-template-areas` | `"slide"` | `"slide"` |
42
+ | `grid-template-columns` | `1240px` | `1240px` |
43
+ | every slide's width | 1240 | 1240 |
44
+ | first slide's `left` | 80 (the track's own left) | **−947** — one has already wrapped round |
45
+
46
+ So the only structural difference is that a carousel keeps a slide positioned
47
+ before the first one, which is both why it wraps and why neither arrow ever hides.
48
+ Showing several slides at once is not something this property gives you.
33
49
 
34
50
  ## Two things the markup does not announce
35
51
 
@@ -77,3 +93,23 @@ Everything is addressed by position inside the slider, for the reasons above.
77
93
  "Which slide is current" is what the tool reads, because `slide` and `crossFade`
78
94
  drive transforms and opacities completely differently and that is the one thing
79
95
  both agree on.
96
+
97
+
98
+ ## Two things the component does not draw for you
99
+
100
+ **An arrow has no glyph.** `<mosaic-slider-arrow-left>` renders with empty
101
+ `innerHTML`, and its placement rule is `none`, so there is nowhere to put an icon
102
+ either. The entire appearance is the author's CSS on the element itself. The
103
+ two-border chevron works and needs nothing else: a ~9px box showing only
104
+ `border-top` and `border-right`, turned with a `rotateZ` transform entry (`45deg`
105
+ for next, `-135deg` for prev).
106
+
107
+ **`.m-slide-content` arrives as `display:flex` with `align-items: center`.** A
108
+ slide whose type should sit against the left edge has to say `alignItems:
109
+ "flex-start"`; without it every slide centres its own content and the result looks
110
+ like exactly the stock carousel you were trying not to build.
111
+
112
+ And one of layout rather than of the component: slider chrome positioned over the
113
+ slides (`position:absolute` bottom bar holding the bullets and arrows) will collide
114
+ with the slide's own text unless the slide reserves a strip of bottom padding for
115
+ it. Nothing warns you; the caption simply runs under the bullets.
@@ -201,9 +201,23 @@ type-switched group, `flexSizing`:
201
201
  "flexSizing": {"type": "custom", "customOptions": {…}}
202
202
  ```
203
203
 
204
- `none` is what you want for a label column that must not be squeezed, and `grow`
205
- for the cell that takes the slack — the pair that `flex-shrink:0` / `flex-grow:1`
206
- would have been.
204
+ **`grow` carries `flex-shrink:0`, so it is not `flex: 1`.** An item set with it
205
+ grows but never yields, and one long string then pushes its whole container wider
206
+ than the viewport — which is a bug a horizontal-overflow check does not catch,
207
+ because the document simply becomes wider rather than scrolling. The emitted CSS:
208
+
209
+ | type | emitted |
210
+ |---|---|
211
+ | `none` | `flex-grow:0; flex-shrink:0` |
212
+ | `shrink` | `flex-grow:0; flex-shrink:1` |
213
+ | `grow` | `flex-grow:1; flex-shrink:0` |
214
+ | `custom` | whichever of `flexGrow` / `flexShrink` you set |
215
+
216
+ `none` is right for a label column that must not be squeezed. For the cell that
217
+ takes the slack AND has to give ground on a phone, `custom` with both set to `1`
218
+ is the only type that says so. Pair it with `minWidth: 0`, because a flex or grid
219
+ item defaults to `min-width: auto` — min-content — and a `white-space: pre` block
220
+ inside one will hold the track open at its longest line.
207
221
 
208
222
  **There is no `overflowX` or `overflowY`** — only the shorthand `overflow`, with
209
223
  `visible | hidden | clip | scroll | auto`. One axis at a time is not expressible
@@ -99,8 +99,10 @@ const counts = {
99
99
  "data/data-class-hierarchy.csv": 125,
100
100
  "data/custom-fields-verification.csv": 62,
101
101
  "data/dialog-verification.csv": 19,
102
- "data/slider-verification.csv": 19,
103
- "data/navigation-verification.csv": 46,
102
+ "data/slider-verification.csv": 30,
103
+ "data/token-verification.csv": 15,
104
+ "data/mobile-audit.csv": 54,
105
+ "data/navigation-verification.csv": 27,
104
106
  "data/page-modal-verification.csv": 19,
105
107
  "data/interaction-rules-verification.csv": 17,
106
108
  "data/dialog-form-verification.csv": 49,