@aglyn/plugins-ai 1.0.0-beta.239 → 1.0.0-beta.241

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 (99) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-job-links.d.ts +17 -0
  3. package/src/lib/components/ai-job-links.js +63 -0
  4. package/src/lib/components/ai-job-links.js.map +1 -1
  5. package/src/lib/components/ai-job-plan.component.js +8 -1
  6. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  7. package/src/lib/components/ai-job-requests.d.ts +5 -0
  8. package/src/lib/components/ai-job-requests.js +4 -2
  9. package/src/lib/components/ai-job-requests.js.map +1 -1
  10. package/src/lib/components/ai-site-build-page.component.js +7 -1
  11. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  12. package/src/lib/components/ai-store-finish-card.component.d.ts +28 -0
  13. package/src/lib/components/ai-store-finish-card.component.js +116 -0
  14. package/src/lib/components/ai-store-finish-card.component.js.map +1 -0
  15. package/src/lib/components/assist-build-card.component.js +12 -1
  16. package/src/lib/components/assist-build-card.component.js.map +1 -1
  17. package/src/lib/components/assist-panel.component.js +14 -2
  18. package/src/lib/components/assist-panel.component.js.map +1 -1
  19. package/src/lib/jobs/ai-job-build-step.d.ts +45 -1
  20. package/src/lib/jobs/ai-job-build-step.js +283 -7
  21. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  22. package/src/lib/jobs/ai-job-drafts.d.ts +9 -1
  23. package/src/lib/jobs/ai-job-drafts.js +4 -1
  24. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  25. package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
  26. package/src/lib/jobs/ai-job-layout-language.js +11 -4
  27. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  28. package/src/lib/jobs/ai-job-layout-step.js +3 -1
  29. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  30. package/src/lib/jobs/ai-job-page-language.d.ts +2 -1
  31. package/src/lib/jobs/ai-job-page-language.js +26 -9
  32. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  33. package/src/lib/jobs/ai-job-page-sections.d.ts +11 -0
  34. package/src/lib/jobs/ai-job-page-sections.js +53 -5
  35. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  36. package/src/lib/jobs/ai-job-page-step.d.ts +2 -1
  37. package/src/lib/jobs/ai-job-page-step.js +51 -10
  38. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  39. package/src/lib/jobs/ai-job-plan-step.d.ts +12 -0
  40. package/src/lib/jobs/ai-job-plan-step.js +41 -7
  41. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  42. package/src/lib/jobs/ai-job-site-datasets.d.ts +89 -2
  43. package/src/lib/jobs/ai-job-site-datasets.js +239 -15
  44. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  45. package/src/lib/jobs/ai-job-site-step.d.ts +18 -2
  46. package/src/lib/jobs/ai-job-site-step.js +109 -22
  47. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  48. package/src/lib/jobs/ai-job-site-store-pages.d.ts +75 -0
  49. package/src/lib/jobs/ai-job-site-store-pages.js +195 -0
  50. package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -0
  51. package/src/lib/jobs/ai-job-store-links-layout.d.ts +71 -0
  52. package/src/lib/jobs/ai-job-store-links-layout.js +95 -0
  53. package/src/lib/jobs/ai-job-store-links-layout.js.map +1 -0
  54. package/src/lib/jobs/ai-layout-stock-photos.d.ts +69 -3
  55. package/src/lib/jobs/ai-layout-stock-photos.js +603 -42
  56. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  57. package/src/lib/jobs/ai-site-publish.d.ts +18 -0
  58. package/src/lib/jobs/ai-site-publish.js +76 -15
  59. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  60. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.d.ts +43 -0
  61. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.js +61 -0
  62. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.js.map +1 -0
  63. package/src/lib/layout-language/ai-layout-compiler.js +25 -2
  64. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  65. package/src/lib/layout-language/ai-layout-frame.d.ts +10 -0
  66. package/src/lib/layout-language/ai-layout-frame.js +58 -4
  67. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  68. package/src/lib/layout-language/ai-layout-listings.d.ts +6 -0
  69. package/src/lib/layout-language/ai-layout-listings.js +3 -0
  70. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  71. package/src/lib/layout-language/ai-layout-pictures.js +12 -4
  72. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  73. package/src/lib/model/ai-build-job.d.ts +2 -0
  74. package/src/lib/model/ai-build-job.js +23 -3
  75. package/src/lib/model/ai-build-job.js.map +1 -1
  76. package/src/lib/model/ai-build-plan.d.ts +7 -0
  77. package/src/lib/model/ai-build-plan.js.map +1 -1
  78. package/src/lib/model/ai-build-progress.js +3 -1
  79. package/src/lib/model/ai-build-progress.js.map +1 -1
  80. package/src/lib/model/ai-build-store-pages.d.ts +133 -0
  81. package/src/lib/model/ai-build-store-pages.js +209 -0
  82. package/src/lib/model/ai-build-store-pages.js.map +1 -0
  83. package/src/lib/model/ai-layout-store-links.d.ts +64 -0
  84. package/src/lib/model/ai-layout-store-links.js +165 -0
  85. package/src/lib/model/ai-layout-store-links.js.map +1 -0
  86. package/src/lib/model/ai-site-inventory.d.ts +5 -0
  87. package/src/lib/model/ai-site-inventory.js.map +1 -1
  88. package/src/lib/model/ai-site-store-pages.d.ts +192 -0
  89. package/src/lib/model/ai-site-store-pages.js +383 -0
  90. package/src/lib/model/ai-site-store-pages.js.map +1 -0
  91. package/src/lib/runtime/ai-doctrine-validators.d.ts +44 -0
  92. package/src/lib/runtime/ai-doctrine-validators.js +177 -0
  93. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  94. package/src/lib/runtime/ai-doctrine.js +3 -2
  95. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  96. package/src/lib/runtime/site-inventory.js +8 -3
  97. package/src/lib/runtime/site-inventory.js.map +1 -1
  98. package/src/lib/testing/stand-in-dataset-index.js +8 -4
  99. package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
@@ -689,6 +689,50 @@ export declare function aiSettleDisagreeingNodes(input: unknown): unknown;
689
689
  * re-ask. Reads and returns the tree as the model wrote it.
690
690
  */
691
691
  export declare function aiSettleCutHeadings(input: unknown): unknown;
692
+ /**
693
+ * THE RULES A CHECK WOULD REFUSE FOR AN ATTRIBUTE, SETTLED IN CODE (AGL-3660).
694
+ *
695
+ * A finding whose fix is one attribute, one level or one key, with nothing to
696
+ * decide about what the page says, is applied before the check reads the
697
+ * tree, as `aiSettleGridItems` sizes a container's items. A live Free guided
698
+ * start's Portfolio page (2026-10-10) was refused for rule 16's `eager-image`:
699
+ * the pictures step loaded every photo of the opening section eagerly, the
700
+ * last pass reads the stored page and asks no model, and Try again could only
701
+ * refuse the same page for the same reason. None of these is a choice a model
702
+ * makes better than the code that knows the answer:
703
+ *
704
+ * - `eager-image` (16): only the tree's first image may load eagerly; every
705
+ * later one has `loading` unset, so the renderer loads it when reached;
706
+ * - `autoplay-video` (16), its attribute half: `autoPlay` and `preload: auto`
707
+ * are taken off. A film with no poster still needs a picture, and is left;
708
+ * - `extra-font` (16): an `sx` font family outside an email is taken off, so
709
+ * the element reads the theme's typography;
710
+ * - `multiple-h1` and `skipped-heading` (11): every top-level heading after a
711
+ * page's first renders as an h2 (a layout's all do, a component's after its
712
+ * first), and a heading after it that skips a level renders one below the
713
+ * heading before it. Only the element changes (`component`): the variant,
714
+ * and so the look, is kept. A heading before a page's h1, and a page with no
715
+ * h1, are the model's to mend;
716
+ * - `multiple-main` and `landmark-in-fragment` (11): a `main` landmark after
717
+ * the first, and any in a component, a form or an email, is unset;
718
+ * - `grid-gap` (12): a Grid container's `sx` gap is taken off and becomes its
719
+ * `spacing` where it sets none, a number gap as itself, anything else 2,
720
+ * which is what the re-ask asks for;
721
+ * - `link-color-on-band` (5): a link or a button whose words are drawn in its
722
+ * band's own family is given that family's contrast text as its `sx` color,
723
+ * which is what the re-ask asks for.
724
+ *
725
+ * A tree with nothing to settle comes back as the same object, so settling is
726
+ * idempotent; a node that is changed is copied, never edited in place.
727
+ */
728
+ export declare function aiSettleMechanicalRules<T extends AiDoctrineTree>(tree: T, outputKind: AiOutputKind): T;
729
+ /**
730
+ * `aiSettleMechanicalRules` on a tree as the model wrote it, before the
731
+ * palette validator reads it (AGL-3660), as `aiSettleWrittenGridItems` settles
732
+ * its items. Anything that is no flat node map comes back as it was, and a
733
+ * node whose shape is not a palette node's is read as nothing and left as written.
734
+ */
735
+ export declare function aiSettleWrittenMechanicalRules(input: unknown, outputKind: AiOutputKind): unknown;
692
736
  export interface AiDoctrineTreeReport {
693
737
  ok: boolean;
694
738
  /** The palette validator's result: the tree to store, its repairs and its id map. */
@@ -2958,6 +2958,183 @@ const BUDGET_METRICS = [
2958
2958
  nodes
2959
2959
  }) : input;
2960
2960
  }
2961
+ // ── Mechanical rules, settled in code (AGL-3660) ─────────────────────────
2962
+ /**
2963
+ * THE RULES A CHECK WOULD REFUSE FOR AN ATTRIBUTE, SETTLED IN CODE (AGL-3660).
2964
+ *
2965
+ * A finding whose fix is one attribute, one level or one key, with nothing to
2966
+ * decide about what the page says, is applied before the check reads the
2967
+ * tree, as `aiSettleGridItems` sizes a container's items. A live Free guided
2968
+ * start's Portfolio page (2026-10-10) was refused for rule 16's `eager-image`:
2969
+ * the pictures step loaded every photo of the opening section eagerly, the
2970
+ * last pass reads the stored page and asks no model, and Try again could only
2971
+ * refuse the same page for the same reason. None of these is a choice a model
2972
+ * makes better than the code that knows the answer:
2973
+ *
2974
+ * - `eager-image` (16): only the tree's first image may load eagerly; every
2975
+ * later one has `loading` unset, so the renderer loads it when reached;
2976
+ * - `autoplay-video` (16), its attribute half: `autoPlay` and `preload: auto`
2977
+ * are taken off. A film with no poster still needs a picture, and is left;
2978
+ * - `extra-font` (16): an `sx` font family outside an email is taken off, so
2979
+ * the element reads the theme's typography;
2980
+ * - `multiple-h1` and `skipped-heading` (11): every top-level heading after a
2981
+ * page's first renders as an h2 (a layout's all do, a component's after its
2982
+ * first), and a heading after it that skips a level renders one below the
2983
+ * heading before it. Only the element changes (`component`): the variant,
2984
+ * and so the look, is kept. A heading before a page's h1, and a page with no
2985
+ * h1, are the model's to mend;
2986
+ * - `multiple-main` and `landmark-in-fragment` (11): a `main` landmark after
2987
+ * the first, and any in a component, a form or an email, is unset;
2988
+ * - `grid-gap` (12): a Grid container's `sx` gap is taken off and becomes its
2989
+ * `spacing` where it sets none, a number gap as itself, anything else 2,
2990
+ * which is what the re-ask asks for;
2991
+ * - `link-color-on-band` (5): a link or a button whose words are drawn in its
2992
+ * band's own family is given that family's contrast text as its `sx` color,
2993
+ * which is what the re-ask asks for.
2994
+ *
2995
+ * A tree with nothing to settle comes back as the same object, so settling is
2996
+ * idempotent; a node that is changed is copied, never edited in place.
2997
+ */ export function aiSettleMechanicalRules(tree, outputKind) {
2998
+ if (!tree.nodes[tree.rootId]) return tree;
2999
+ const nodes = _extends({}, tree.nodes);
3000
+ const copied = new Set();
3001
+ const edit = (id)=>{
3002
+ if (!copied.has(id)) {
3003
+ const node = nodes[id];
3004
+ nodes[id] = _extends({}, node, node.props ? {
3005
+ props: _extends({}, node.props)
3006
+ } : {}, node.sx ? {
3007
+ sx: _extends({}, node.sx)
3008
+ } : {});
3009
+ copied.add(id);
3010
+ }
3011
+ return nodes[id];
3012
+ };
3013
+ const unsetProp = (id, name)=>{
3014
+ var _nodes_id_props;
3015
+ if (((_nodes_id_props = nodes[id].props) == null ? void 0 : _nodes_id_props[name]) === undefined) return;
3016
+ delete edit(id).props[name];
3017
+ };
3018
+ const setProp = (id, name, value)=>{
3019
+ var _node_props;
3020
+ var _nodes_id_props;
3021
+ if (((_nodes_id_props = nodes[id].props) == null ? void 0 : _nodes_id_props[name]) === value) return;
3022
+ const node = edit(id);
3023
+ node.props = _extends({}, (_node_props = node.props) != null ? _node_props : {}, {
3024
+ [name]: value
3025
+ });
3026
+ };
3027
+ const unsetSx = (id, name)=>{
3028
+ var _nodes_id_sx;
3029
+ if (((_nodes_id_sx = nodes[id].sx) == null ? void 0 : _nodes_id_sx[name]) === undefined) return;
3030
+ delete edit(id).sx[name];
3031
+ };
3032
+ const visits = walkTree(tree);
3033
+ const email = outputKind === 'email';
3034
+ const framed = !email && outputKind !== 'form';
3035
+ const fragment = outputKind === 'component' || email || outputKind === 'form';
3036
+ let images = 0;
3037
+ let mains = 0;
3038
+ for (const { id, node } of visits){
3039
+ var _node_sx, _node_props;
3040
+ // Rule 16: one eager image, the first; the rest load when they are reached.
3041
+ if (node.componentId === 'image') {
3042
+ var _node_props1;
3043
+ images += 1;
3044
+ if (images > 1 && ((_node_props1 = node.props) == null ? void 0 : _node_props1['loading']) === 'eager') unsetProp(id, 'loading');
3045
+ }
3046
+ // Rule 16: a film loads nothing before it is played.
3047
+ if (node.componentId === 'video') {
3048
+ var _node_props2, _node_props3;
3049
+ if (((_node_props2 = node.props) == null ? void 0 : _node_props2['autoPlay']) === true) unsetProp(id, 'autoPlay');
3050
+ if (((_node_props3 = node.props) == null ? void 0 : _node_props3['preload']) === 'auto') unsetProp(id, 'preload');
3051
+ }
3052
+ // Rule 16: the theme's typography, outside an email.
3053
+ if (!email && ((_node_sx = node.sx) == null ? void 0 : _node_sx['fontFamily']) !== undefined) unsetSx(id, 'fontFamily');
3054
+ // Rule 11: one main landmark, and none in what is placed inside pages.
3055
+ if (elementOf(node) === 'main') {
3056
+ mains += 1;
3057
+ if (fragment || mains > 1) {
3058
+ var _node_props4, _node_props5;
3059
+ if (((_node_props4 = node.props) == null ? void 0 : _node_props4['component']) === 'main') unsetProp(id, 'component');
3060
+ if (((_node_props5 = node.props) == null ? void 0 : _node_props5['element']) === 'main') unsetProp(id, 'element');
3061
+ }
3062
+ }
3063
+ // Rule 12: a container is spaced by its `spacing`, never an sx gap.
3064
+ if (framed && node.componentId === GRID && ((_node_props = node.props) == null ? void 0 : _node_props['container']) === true) {
3065
+ const gap = GRID_GAP_SX_KEYS.find((key)=>{
3066
+ var _node_sx;
3067
+ return ((_node_sx = node.sx) == null ? void 0 : _node_sx[key]) !== undefined;
3068
+ });
3069
+ if (gap) {
3070
+ var _node_sx1, _node_props6;
3071
+ const value = (_node_sx1 = node.sx) == null ? void 0 : _node_sx1[gap];
3072
+ for (const key of GRID_GAP_SX_KEYS)unsetSx(id, key);
3073
+ if (((_node_props6 = node.props) == null ? void 0 : _node_props6['spacing']) === undefined) setProp(id, 'spacing', typeof value === 'number' ? value : 2);
3074
+ }
3075
+ }
3076
+ }
3077
+ // Rule 11: the heading outline, in document order.
3078
+ if (framed) {
3079
+ const headings = visits.map((visit)=>({
3080
+ id: visit.id,
3081
+ level: aiHeadingLevel(nodes[visit.id])
3082
+ })).filter((entry)=>entry.level !== null);
3083
+ const page = outputKind === 'page' || outputKind === 'template';
3084
+ const firstH1 = headings.findIndex((heading)=>heading.level === 1);
3085
+ // A page's outline is settled from its h1 on; a layout's and a component's from their first heading.
3086
+ const start = page ? firstH1 : 0;
3087
+ let previous = page ? 0 : null;
3088
+ for(let index = Math.max(start, 0); start >= 0 && index < headings.length; index += 1){
3089
+ const heading = headings[index];
3090
+ // A page keeps its first h1, a component its first, and a layout none.
3091
+ const keepsH1 = outputKind !== 'layout' && index === firstH1;
3092
+ let level = heading.level === 1 && !keepsH1 ? 2 : heading.level;
3093
+ previous != null ? previous : previous = level - 1;
3094
+ if (level > previous + 1) level = previous + 1;
3095
+ if (level !== heading.level) setProp(heading.id, 'component', `h${level}`);
3096
+ previous = level;
3097
+ }
3098
+ }
3099
+ // Rule 5: a link's words in its band's contrast text, read on the tree as settled so far.
3100
+ if (framed) {
3101
+ const settled = {
3102
+ rootId: tree.rootId,
3103
+ nodes
3104
+ };
3105
+ for (const visit of walkTree(settled)){
3106
+ var _node_sx2;
3107
+ if (!LINK_COMPONENTS.has(visit.node.componentId)) continue;
3108
+ const family = linkWordsFamily(visit.node);
3109
+ const band = family ? bandOf(settled, visit) : null;
3110
+ if (!band || band.family !== family) continue;
3111
+ const node = edit(visit.id);
3112
+ node.sx = _extends({}, (_node_sx2 = node.sx) != null ? _node_sx2 : {}, {
3113
+ color: `${family}.contrastText`
3114
+ });
3115
+ }
3116
+ }
3117
+ return copied.size ? _extends({}, tree, {
3118
+ nodes
3119
+ }) : tree;
3120
+ }
3121
+ /**
3122
+ * `aiSettleMechanicalRules` on a tree as the model wrote it, before the
3123
+ * palette validator reads it (AGL-3660), as `aiSettleWrittenGridItems` settles
3124
+ * its items. Anything that is no flat node map comes back as it was, and a
3125
+ * node whose shape is not a palette node's is read as nothing and left as written.
3126
+ */ export function aiSettleWrittenMechanicalRules(input, outputKind) {
3127
+ if (!isRecord(input) || typeof input['rootId'] !== 'string' || !isRecord(input['nodes'])) return input;
3128
+ const readable = Object.entries(input['nodes']).filter(([, node])=>isRecord(node) && typeof node['componentId'] === 'string' && (node['props'] === undefined || isRecord(node['props'])) && (node['sx'] === undefined || isRecord(node['sx'])) && (node['nodes'] === undefined || Array.isArray(node['nodes']) && node['nodes'].every((child)=>typeof child === 'string')));
3129
+ const tree = {
3130
+ rootId: input['rootId'],
3131
+ nodes: Object.fromEntries(readable)
3132
+ };
3133
+ const settled = aiSettleMechanicalRules(tree, outputKind);
3134
+ return settled === tree ? input : _extends({}, input, {
3135
+ nodes: _extends({}, input['nodes'], settled.nodes)
3136
+ });
3137
+ }
2961
3138
  /**
2962
3139
  * The whole check on one emitted tree: the palette validator first, then
2963
3140
  * every tree rule, then the budget. `otherPages` are the trees generated