@aglyn/plugins-ai 1.0.0-beta.242 → 1.0.0-beta.244

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 (27) hide show
  1. package/package.json +17 -17
  2. package/src/lib/jobs/ai-job-layout-language.js +11 -6
  3. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  4. package/src/lib/jobs/ai-job-page-language.d.ts +3 -1
  5. package/src/lib/jobs/ai-job-page-language.js +17 -5
  6. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  7. package/src/lib/jobs/ai-job-page-step.js +11 -4
  8. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  9. package/src/lib/jobs/ai-job-site-step.js +22 -8
  10. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  11. package/src/lib/jobs/ai-layout-stock-photos.d.ts +52 -8
  12. package/src/lib/jobs/ai-layout-stock-photos.js +351 -72
  13. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  14. package/src/lib/jobs/ai-site-publish.d.ts +14 -0
  15. package/src/lib/jobs/ai-site-publish.js +28 -3
  16. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  17. package/src/lib/jobs/fixtures/ai-stock-photo-tidewater-recordings.d.ts +27 -0
  18. package/src/lib/jobs/fixtures/ai-stock-photo-tidewater-recordings.js +261 -0
  19. package/src/lib/jobs/fixtures/ai-stock-photo-tidewater-recordings.js.map +1 -0
  20. package/src/lib/layout-language/ai-layout-links.js +5 -2
  21. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  22. package/src/lib/runtime/ai-doctrine-validators.d.ts +42 -1
  23. package/src/lib/runtime/ai-doctrine-validators.js +98 -11
  24. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  25. package/src/lib/usage/ai-quota-checks.d.ts +13 -3
  26. package/src/lib/usage/ai-quota-checks.js +46 -8
  27. package/src/lib/usage/ai-quota-checks.js.map +1 -1
@@ -130,6 +130,14 @@ export interface AiDoctrineTreeContext extends AiNodeTreeContext {
130
130
  * to when the site has none for what it promises. Absent: none is known.
131
131
  */
132
132
  homeScreenIds?: readonly string[];
133
+ /**
134
+ * The site's pages that place its form, by id (AGL-3729): where a link whose
135
+ * words ask a visitor to get in touch (`aiAsksToGetInTouch`) does what it
136
+ * says, even when that page is home. A guided start's café put its
137
+ * pre-order form on Home, and its Menu page's "Request weekend pickup"
138
+ * links there. Absent: none is known, and a link home is held to its words.
139
+ */
140
+ formScreenIds?: readonly string[];
133
141
  /**
134
142
  * The node as the model wrote it, by the id the checked tree gives it
135
143
  * (AGL-3078), for a tree that was stored before it is checked: a page
@@ -472,6 +480,39 @@ export declare function isAiLinkElement(componentId: unknown): boolean;
472
480
  * a re-ask to inherit the band's contrast text or to set it.
473
481
  */
474
482
  export declare function detectInvisibleLinks(tree: AiDoctrineTree, outputKind: AiOutputKind): AiDoctrineViolation[];
483
+ /**
484
+ * Whether a link's words ask a visitor to get in touch — contact, book, quote,
485
+ * request, order, pre-order, enquire — which the site's form answers
486
+ * (AGL-3729). The layout language sends such a link to the form.
487
+ */
488
+ export declare function aiAsksToGetInTouch(label: string): boolean;
489
+ /**
490
+ * RULE 10'S LINK SENT HOME, REPAIRED IN CODE (AGL-3729).
491
+ *
492
+ * The rule's own advice is "link the page that does, or leave the link out",
493
+ * and both halves are something code can do once it knows where the site's
494
+ * form is. A Free guided start's café (beta.243, 2026-10-12) put its contact
495
+ * and pre-order form on Home; its Menu page's "Request weekend pickup" was
496
+ * sent to Home, and the page was refused on every answer, since the
497
+ * destination was the compiler's, not the model's. So, before the check reads
498
+ * the tree, each link `detectUnrelatedScreenLinks` would refuse is:
499
+ *
500
+ * - repointed to the page that places the site's form, when its words ask a
501
+ * visitor to get in touch and the site has such a page that is not the page
502
+ * being checked (`formScreenIds`, `ownScreenIds`). The platform reaches a
503
+ * section of another page by no anchor (AGL-2867), so the link names the
504
+ * page; a link to a section of its own page is the Scroll to element
505
+ * interaction, which this never writes;
506
+ * - otherwise left out: the node and everything under it are taken off the
507
+ * page, and out of its parent's children.
508
+ *
509
+ * The root is never taken out. A tree with nothing to repair comes back as
510
+ * the same object; a node that is changed is copied, never edited in place.
511
+ */
512
+ export declare function aiSettleUnrelatedScreenLinks<T extends AiDoctrineTree>(tree: T, outputKind: AiOutputKind, context?: Pick<AiDoctrineTreeContext, 'homeScreenIds' | 'formScreenIds'> & {
513
+ /** The page being checked, by every id it goes by: a link never goes to itself. */
514
+ ownScreenIds?: readonly string[];
515
+ }): T;
475
516
  /**
476
517
  * Rule 10, for a link sent where the site has nothing for it (AGL-3056). A
477
518
  * link whose purpose the site has no screen for is left out; it never goes to
@@ -483,7 +524,7 @@ export declare function detectInvisibleLinks(tree: AiDoctrineTree, outputKind: A
483
524
  * not say home is refused, with a re-ask to link the screen that does what it
484
525
  * says, or to leave the link out.
485
526
  */
486
- export declare function detectUnrelatedScreenLinks(tree: AiDoctrineTree, outputKind: AiOutputKind, context?: Pick<AiDoctrineTreeContext, 'homeScreenIds'>): AiDoctrineViolation[];
527
+ export declare function detectUnrelatedScreenLinks(tree: AiDoctrineTree, outputKind: AiOutputKind, context?: Pick<AiDoctrineTreeContext, 'homeScreenIds' | 'formScreenIds'>): AiDoctrineViolation[];
487
528
  /**
488
529
  * Rule 10, for a link that goes nowhere (AGL-3072). A Button or a Screen Link
489
530
  * goes where its `screenId` or its `href` says, and those are the only
@@ -1841,20 +1841,23 @@ const GRID = 'muiGrid';
1841
1841
  ];
1842
1842
  }
1843
1843
  /** A link's words that say it goes home. */ const HOME_WORDS = /\bhome(?:\s?page)?\b/i;
1844
+ /** Words that ask a visitor to get in touch, which the site's form answers. */ const GET_IN_TOUCH_WORDS = /\b(?:contact|book|booking|call|quote|estimate|appointment|schedule|reserve|reservation|get in touch|request|visit|enquire|inquire|enquiry|inquiry|message|talk|consult|consultation|order|pre-?order|sign up|join|apply|start)\b/i;
1844
1845
  /**
1845
- * Rule 10, for a link sent where the site has nothing for it (AGL-3056). A
1846
- * link whose purpose the site has no screen for is left out; it never goes to
1847
- * a screen that does something else. The site inventory cannot say what each
1848
- * screen is for, but it can name the one a model falls back to: a live About
1849
- * page's footer sent "Request a Consultation" to the home screen, because the
1850
- * site had no consultation screen. So a Screen Link or a Button, outside the
1851
- * header and the navigation, that links the home screen with words that do
1852
- * not say home is refused, with a re-ask to link the screen that does what it
1853
- * says, or to leave the link out.
1854
- */ export function detectUnrelatedScreenLinks(tree, outputKind, context = {}) {
1855
- var _context_homeScreenIds;
1846
+ * Whether a link's words ask a visitor to get in touch — contact, book, quote,
1847
+ * request, order, pre-order, enquire — which the site's form answers
1848
+ * (AGL-3729). The layout language sends such a link to the form.
1849
+ */ export function aiAsksToGetInTouch(label) {
1850
+ return GET_IN_TOUCH_WORDS.test(label);
1851
+ }
1852
+ /**
1853
+ * The links rule 10 refuses as sent home for nothing (`detectUnrelatedScreenLinks`),
1854
+ * in document order. A link whose words ask a visitor to get in touch, sent to
1855
+ * a home page that places the site's form, does what it says (AGL-3729).
1856
+ */ function unrelatedScreenLinks(tree, outputKind, context) {
1857
+ var _context_homeScreenIds, _context_formScreenIds;
1856
1858
  if (outputKind === 'email' || outputKind === 'form') return [];
1857
1859
  const home = new Set((_context_homeScreenIds = context.homeScreenIds) != null ? _context_homeScreenIds : []);
1860
+ const forms = new Set((_context_formScreenIds = context.formScreenIds) != null ? _context_formScreenIds : []);
1858
1861
  const sent = [];
1859
1862
  for (const visit of walkTree(tree)){
1860
1863
  var _node_props, _node_props1, _node_props2;
@@ -1864,6 +1867,8 @@ const GRID = 'muiGrid';
1864
1867
  const linksHome = typeof screenId === 'string' && home.has(screenId) || ((_node_props1 = node.props) == null ? void 0 : _node_props1['href']) === '/';
1865
1868
  const label = typeof ((_node_props2 = node.props) == null ? void 0 : _node_props2['children']) === 'string' ? node.props['children'].trim() : '';
1866
1869
  if (!linksHome || !label || HOME_WORDS.test(label)) continue;
1870
+ // Home holds the site's form, and the words ask for what it does (AGL-3729).
1871
+ if (typeof screenId === 'string' && forms.has(screenId) && aiAsksToGetInTouch(label)) continue;
1867
1872
  // The header and the navigation link home by name and by the site's own brand.
1868
1873
  const inNavigation = hasAncestor(tree, visit, (ancestor)=>ancestor.componentId === 'muiAppBar' || [
1869
1874
  'header',
@@ -1874,6 +1879,88 @@ const GRID = 'muiGrid';
1874
1879
  label
1875
1880
  });
1876
1881
  }
1882
+ return sent;
1883
+ }
1884
+ /**
1885
+ * RULE 10'S LINK SENT HOME, REPAIRED IN CODE (AGL-3729).
1886
+ *
1887
+ * The rule's own advice is "link the page that does, or leave the link out",
1888
+ * and both halves are something code can do once it knows where the site's
1889
+ * form is. A Free guided start's café (beta.243, 2026-10-12) put its contact
1890
+ * and pre-order form on Home; its Menu page's "Request weekend pickup" was
1891
+ * sent to Home, and the page was refused on every answer, since the
1892
+ * destination was the compiler's, not the model's. So, before the check reads
1893
+ * the tree, each link `detectUnrelatedScreenLinks` would refuse is:
1894
+ *
1895
+ * - repointed to the page that places the site's form, when its words ask a
1896
+ * visitor to get in touch and the site has such a page that is not the page
1897
+ * being checked (`formScreenIds`, `ownScreenIds`). The platform reaches a
1898
+ * section of another page by no anchor (AGL-2867), so the link names the
1899
+ * page; a link to a section of its own page is the Scroll to element
1900
+ * interaction, which this never writes;
1901
+ * - otherwise left out: the node and everything under it are taken off the
1902
+ * page, and out of its parent's children.
1903
+ *
1904
+ * The root is never taken out. A tree with nothing to repair comes back as
1905
+ * the same object; a node that is changed is copied, never edited in place.
1906
+ */ export function aiSettleUnrelatedScreenLinks(tree, outputKind, context = {}) {
1907
+ var _context_ownScreenIds, _find, _context_formScreenIds;
1908
+ const sent = unrelatedScreenLinks(tree, outputKind, context);
1909
+ if (!sent.length) return tree;
1910
+ const own = new Set((_context_ownScreenIds = context.ownScreenIds) != null ? _context_ownScreenIds : []);
1911
+ const formScreen = (_find = ((_context_formScreenIds = context.formScreenIds) != null ? _context_formScreenIds : []).find((id)=>!own.has(id))) != null ? _find : null;
1912
+ const nodes = _extends({}, tree.nodes);
1913
+ const parents = new Map();
1914
+ for (const visit of walkTree(tree)){
1915
+ const parent = visit.ancestors[visit.ancestors.length - 1];
1916
+ if (parent !== undefined) parents.set(visit.id, parent);
1917
+ }
1918
+ for (const { id, label } of sent){
1919
+ var _nodes_parent_nodes;
1920
+ if (!nodes[id]) continue;
1921
+ if (formScreen && aiAsksToGetInTouch(label)) {
1922
+ var _nodes_id_props;
1923
+ const props = _extends({}, (_nodes_id_props = nodes[id].props) != null ? _nodes_id_props : {}, {
1924
+ screenId: formScreen
1925
+ });
1926
+ delete props['href'];
1927
+ nodes[id] = _extends({}, nodes[id], {
1928
+ props
1929
+ });
1930
+ continue;
1931
+ }
1932
+ const parent = parents.get(id);
1933
+ if (parent === undefined || !nodes[parent]) continue;
1934
+ nodes[parent] = _extends({}, nodes[parent], {
1935
+ nodes: ((_nodes_parent_nodes = nodes[parent].nodes) != null ? _nodes_parent_nodes : []).filter((child)=>child !== id)
1936
+ });
1937
+ const stack = [
1938
+ id
1939
+ ];
1940
+ while(stack.length){
1941
+ var _ref;
1942
+ var _nodes_next;
1943
+ const next = stack.pop();
1944
+ stack.push(...(_ref = (_nodes_next = nodes[next]) == null ? void 0 : _nodes_next.nodes) != null ? _ref : []);
1945
+ delete nodes[next];
1946
+ }
1947
+ }
1948
+ return _extends({}, tree, {
1949
+ nodes
1950
+ });
1951
+ }
1952
+ /**
1953
+ * Rule 10, for a link sent where the site has nothing for it (AGL-3056). A
1954
+ * link whose purpose the site has no screen for is left out; it never goes to
1955
+ * a screen that does something else. The site inventory cannot say what each
1956
+ * screen is for, but it can name the one a model falls back to: a live About
1957
+ * page's footer sent "Request a Consultation" to the home screen, because the
1958
+ * site had no consultation screen. So a Screen Link or a Button, outside the
1959
+ * header and the navigation, that links the home screen with words that do
1960
+ * not say home is refused, with a re-ask to link the screen that does what it
1961
+ * says, or to leave the link out.
1962
+ */ export function detectUnrelatedScreenLinks(tree, outputKind, context = {}) {
1963
+ const sent = unrelatedScreenLinks(tree, outputKind, context);
1877
1964
  if (!sent.length) return [];
1878
1965
  return [
1879
1966
  {