// AI Helper Tool Registry v1.0
// Single source of truth for all tool metadata. Read by meta-tools
// (list_tools, get_tool_doc, get_tool_schema, get_tool_examples,
// search_tool_docs, etc.). All content in English (LLM-friendly).
//
// Loaded as content script (manifest.json content_scripts) — must use a
// global window assignment, NOT ES module export, since content scripts
// cannot use import/export syntax.
//
// Per-entry schema (mirror of tool-registry-content-brief.md):
//   {
//     group:           'meta'|'dom'|'tree'|'bars'|'temp'|'panes'|'consoles'|'interaction'|'ui'|'vision',
//     classification:  'discovery'|'action',
//     purpose:         string,                       // 1-2 sentences
//     inputSchema:     { paramName: { type, required?, default?, description, enum? } },
//     outputSchema:    { data: <shape description> },
//     examples:        [{ task, input, output? }],
//     whenToUse:       string[],                     // 2-4 entries
//     whenNotToUse:    string[],                     // 1-3 entries
//     relatedTools:    string[],                     // 2-5 entries
//     styleFields?:    { Identity, Style, Layout, Behavior?, Hierarchy }, // update_* only
//     requiresConfirmation?: boolean,                // create_*/update_*/update_pane_code only
//     handler:         string,
//                      //  - existing fn name in content.js (e.g. '_aiToolListStructure')
//                      //  - 'window.__aiPageTools.<name>' for MAIN-world page tools
//                      //  - '_aiToolReadEntity:<type>'  / '_aiToolUpdateEntity:<type>' for entity dispatchers
//                      //  - '_aiToolCreateEntity' for the multi-entity create dispatcher
//   }
//
// Standard response envelope (NOT documented per-entry — applies to every tool):
//   { data, meta: { truncated?, count?, limit?, elapsedMs?, blocked?, error? }, suggestions?, warnings? }

window.JZ_AI_TOOL_REGISTRY = {

  // ════════════════════════════════════════════════════════════════════════
  // GROUP: meta (6) — documentation discovery
  // ════════════════════════════════════════════════════════════════════════

  list_tool_groups: {
    group: 'meta',
    classification: 'discovery',
    purpose: 'Returns the list of all tool groups (categories) with their IDs, names, descriptions, and tool counts. Use this first to orient yourself before drilling into a specific group.',
    inputSchema: {},
    outputSchema: {
      data: 'Array<{ id: string, name: string, description: string, toolCount: number }>',
    },
    examples: [
      {
        task: 'Get a one-screen map of all available tool categories at session start',
        input: {},
        output: '[{id:"dom",name:"Page inspection",toolCount:15}, {id:"tree",name:"Folders/groups/rules",toolCount:10}, ...]',
      },
    ],
    whenToUse: [
      'At session start to orient yourself before searching for a specific tool',
      'When you do not know which group contains the capability you need',
      'When the user asks "what can you do" at a high level',
    ],
    whenNotToUse: [
      'When you already know the exact tool name — call get_tool_doc directly',
      'When you need the actual tool list — call list_tools instead',
    ],
    relatedTools: ['list_tools', 'search_tool_docs', 'get_tool_doc'],
    handler: '_aiToolListToolGroups',
  },

  list_tools: {
    group: 'meta',
    classification: 'discovery',
    purpose: 'Returns tools in a single group (or all tools when group is omitted) as a brief table of contents — name, purpose, input summary, output summary. Use this to scan options without paying full doc tokens.',
    inputSchema: {
      group: {
        type: 'string',
        required: false,
        description: 'Group ID to filter by (e.g. "dom", "tree"). Omit to list every tool.',
        enum: ['meta', 'dom', 'tree', 'bars', 'temp', 'panes', 'consoles', 'interaction', 'ui', 'vision'],
      },
    },
    outputSchema: {
      data: 'Array<{ name: string, group: string, purpose: string, inputSummary: string, outputSummary: string, classification: "discovery"|"action" }>',
    },
    examples: [
      { task: 'List all DOM inspection tools', input: { group: 'dom' } },
      { task: 'Survey every tool (no filter)', input: {} },
    ],
    whenToUse: [
      'After list_tool_groups to drill into a chosen category',
      'When comparing siblings (e.g. several read_* tools) before picking one',
      'When the task is fuzzy and you want a menu rather than search',
    ],
    whenNotToUse: [
      'When you have a clear task description — search_tool_docs ranks results by relevance',
      'When you already chose a tool — get_tool_doc is the next step',
    ],
    relatedTools: ['list_tool_groups', 'search_tool_docs', 'get_tool_doc'],
    handler: '_aiToolListTools',
  },

  get_tool_doc: {
    group: 'meta',
    classification: 'discovery',
    purpose: 'Returns full documentation for one tool. Level 1 = brief (purpose + arg names), level 2 = +whenToUse + one example, level 3 = +full schemas + all examples + edge cases + style fields.',
    inputSchema: {
      name: { type: 'string', required: true, description: 'Tool name (e.g. "search_elements")' },
      level: { type: 'number', required: false, default: 2, description: '1 = brief, 2 = usage, 3 = full', enum: [1, 2, 3] },
    },
    outputSchema: {
      data: '{ name, group, classification, purpose, inputSchema?, outputSchema?, examples?, whenToUse?, whenNotToUse?, relatedTools?, styleFields?, requiresConfirmation? }',
    },
    examples: [
      { task: 'Get standard usage doc for search_elements', input: { name: 'search_elements', level: 2 } },
      { task: 'Full documentation for update_bar including style fields', input: { name: 'update_bar', level: 3 } },
    ],
    whenToUse: [
      'After list_tools or search_tool_docs identifies a candidate',
      'Before calling an unfamiliar tool to confirm exact arg names',
      'When you need the full styleFields paragraph for an update_* tool',
    ],
    whenNotToUse: [
      'When you only need argument types — use get_tool_schema (smaller payload)',
      'When you only need examples — use get_tool_examples',
    ],
    relatedTools: ['get_tool_schema', 'get_tool_examples', 'list_tools', 'search_tool_docs'],
    handler: '_aiToolGetToolDoc',
  },

  get_tool_schema: {
    group: 'meta',
    classification: 'discovery',
    purpose: 'Returns only the input/output schemas for one tool — minimal payload when you know the tool but forgot exact argument names or types.',
    inputSchema: {
      name: { type: 'string', required: true, description: 'Tool name' },
    },
    outputSchema: {
      data: '{ name: string, inputSchema: object, outputSchema: object }',
    },
    examples: [
      { task: 'Confirm exact argument names for inspect_element', input: { name: 'inspect_element' } },
    ],
    whenToUse: [
      'When you recall the tool but not the precise arg shape',
      'In tight loops where token budget matters and full doc is overkill',
    ],
    whenNotToUse: [
      'When you also need whenToUse / examples — get_tool_doc level 2 covers both',
    ],
    relatedTools: ['get_tool_doc', 'get_tool_examples'],
    handler: '_aiToolGetToolSchema',
  },

  get_tool_examples: {
    group: 'meta',
    classification: 'discovery',
    purpose: 'Returns concrete usage examples for one tool, optionally filtered by task type. Useful when the doc itself is clear but you want a worked example.',
    inputSchema: {
      name: { type: 'string', required: true, description: 'Tool name' },
      taskType: { type: 'string', required: false, description: 'Optional filter for example category, e.g. "selector-search" or "form-handling"' },
    },
    outputSchema: {
      data: 'Array<{ task: string, input: object, output?: string }>',
    },
    examples: [
      { task: 'See call patterns for simulate_form_fill', input: { name: 'simulate_form_fill' } },
    ],
    whenToUse: [
      'When the schema is clear but you want to mirror a proven invocation',
      'When the user described a task and you want pattern matches',
    ],
    whenNotToUse: [
      'When you need policy/edge case info — use get_tool_doc level 3',
    ],
    relatedTools: ['get_tool_doc', 'get_tool_schema'],
    handler: '_aiToolGetToolExamples',
  },

  search_tool_docs: {
    group: 'meta',
    classification: 'discovery',
    purpose: 'Keyword/semantic search across all tool purposes + whenToUse fields. Returns a ranked list of best matches with one-line reason per hit.',
    inputSchema: {
      query: { type: 'string', required: true, description: 'Natural-language description of what you want to do (e.g. "change bar color", "find buttons by text")' },
    },
    outputSchema: {
      data: 'Array<{ toolName: string, group: string, relevance: number, reason: string }>',
    },
    examples: [
      { task: 'Find tools that change the appearance of an action bar', input: { query: 'change bar appearance color' } },
      { task: 'Find a way to confirm something with the user inline', input: { query: 'ask user for confirmation' } },
    ],
    whenToUse: [
      'When you cannot guess the tool name and need ranked suggestions',
      'When the task description uses different vocabulary than the docs',
      'Instead of reading every list_tools entry sequentially',
    ],
    whenNotToUse: [
      'When you already know the tool — call get_tool_doc directly',
      'When you want categorical browsing — use list_tool_groups',
    ],
    relatedTools: ['list_tools', 'get_tool_doc'],
    handler: '_aiToolSearchToolDocs',
  },

  // ════════════════════════════════════════════════════════════════════════
  // GROUP: dom (15) — page inspection / preview / DOM observation
  // ════════════════════════════════════════════════════════════════════════

  search_elements: {
    group: 'dom',
    classification: 'discovery',
    purpose: 'Find DOM elements by a SPECIFIC filter (text / id-or-class-or-attribute selector / role / attributes). Goal: find a PATTERN to act on, not enumerate the DOM — 5-10 samples + totalMatched are enough to write a rule. Requires at least one specific filter; bare wildcards ("main *", "div") are rejected. Container elements return a structural summary (childCount, descendantTags, textLength) instead of a misleading text slice; leaf elements return their text.',
    inputSchema: {
      text: { type: 'string', required: false, description: 'Substring match against element text content AND accessible-name attributes (aria-label/alt/title/placeholder). Case- and diacritics-insensitive (e.g. "naglowek" matches "nagłówek"). For non-text identity prefer role/selector/attributes.' },
      textExact: { type: 'boolean', required: false, default: false, description: 'Match text exactly (trimmed) instead of substring' },
      selector: { type: 'string', required: false, description: 'CSS selector. Counts as a specific filter only if it has an id (#), class (.) or attribute ([...]). A bare tag/wildcard does not.' },
      role: { type: 'string', required: false, description: 'ARIA role (e.g. "button", "navigation")' },
      tag: { type: 'string', required: false, description: 'HTML tag name — NOT specific on its own, combine with another filter or root' },
      attributes: { type: 'object', required: false, description: 'Map of attribute name to expected value, e.g. {"data-product-id":"123"}' },
      root: { type: 'string', required: false, description: 'CSS selector of a container to search within (scope). Use for hints like "in the header" → root:"header".' },
      visibleOnly: { type: 'boolean', required: false, default: true, description: 'Drop display:none / visibility:hidden / zero-dimension elements' },
      inViewport: { type: 'boolean', required: false, default: false, description: 'Keep only elements currently within the viewport' },
      limit: { type: 'number', required: false, default: 5, description: 'Max results (hard cap 10)' },
      offset: { type: 'number', required: false, default: 0, description: 'Pagination offset within the matched set' },
    },
    outputSchema: {
      data: 'Array<{ selector, tag, classes, attributes, rect } & (leaf: { text } | container: { childCount, descendantCount, textLength, firstWords })>',
    },
    examples: [
      { task: 'Find "Add to cart" buttons (leaf — returns text)', input: { text: 'Add to cart', tag: 'button', visibleOnly: true } },
      { task: 'Find product cards by data attribute', input: { selector: '[data-product-id]', visibleOnly: true } },
      { task: 'Find a banner inside the header (scoped)', input: { root: 'header', role: 'banner' } },
    ],
    whenToUse: [
      'You can describe the element by text, a specific selector, role, or attributes',
      'You want to find a repeatable pattern (selector/attribute) to build a rule on',
    ],
    whenNotToUse: [
      'You only need a count of a broad query — use count_elements (no specific-filter requirement)',
      'You already have a selector and need full detail — use inspect_element',
      'You need the structural shape of a container — use get_dom_outline',
    ],
    relatedTools: ['count_elements', 'inspect_element', 'test_selector', 'get_dom_outline'],
    handler: 'window.__aiPageTools.searchElements',
  },

  count_elements: {
    group: 'dom',
    classification: 'discovery',
    purpose: 'Lightweight sanity check — returns ONLY the count of elements matching a filter (no per-element payload). Permissive (no specific-filter requirement). Use BEFORE a broad search_elements to decide whether the query is narrow enough.',
    inputSchema: {
      text: { type: 'string', required: false, description: 'Substring text match' },
      selector: { type: 'string', required: false, description: 'CSS selector (default "*")' },
      role: { type: 'string', required: false, description: 'ARIA role' },
      tag: { type: 'string', required: false, description: 'HTML tag name' },
      attributes: { type: 'object', required: false, description: 'Attribute name→value map' },
      root: { type: 'string', required: false, description: 'Container selector to scope within' },
      visibleOnly: { type: 'boolean', required: false, default: true, description: 'Count only visible elements' },
    },
    outputSchema: {
      data: '{ count: number, hasOverflow: boolean }  // count capped at 5000',
    },
    examples: [
      { task: 'How many images under main?', input: { root: 'main', tag: 'img' } },
      { task: 'Is ".ad-banner" a good selector?', input: { selector: '.ad-banner' } },
    ],
    whenToUse: [
      'Before a broad search_elements, to see if the filter is narrow enough',
      'To decide if a selector targets one element or hundreds',
    ],
    whenNotToUse: [
      'When you need the actual elements — use search_elements',
    ],
    relatedTools: ['search_elements', 'test_selector'],
    handler: 'window.__aiPageTools.countElements',
  },

  inspect_element: {
    group: 'dom',
    classification: 'discovery',
    purpose: 'Returns rich detail for one element: tag, classes, attributes, parent, children, siblings, bounding rect, visibility, computed styles (selected props), and the count of matching CSS rules.',
    inputSchema: {
      selector: { type: 'string', required: true, description: 'CSS selector for exactly one element (querySelector — first match)' },
      includeStyles: { type: 'boolean', required: false, default: true, description: 'Include ~30 most useful computedStyles' },
      includeChildren: { type: 'boolean', required: false, default: true, description: 'Include immediate child summaries' },
      childrenLimit: { type: 'number', required: false, default: 10, description: 'Cap on immediate children returned' },
    },
    outputSchema: {
      data: '{ selector, tag, id, classes, attributes, text, htmlPreview, parent, children, siblings:{prev,next}, rect, visible, computedStyles?, cssRulesCount }',
    },
    examples: [
      { task: 'Deep-dive on a single button before deciding how to style it', input: { selector: 'button.primary' } },
      { task: 'Cheap variant without computed styles', input: { selector: '#main', includeStyles: false } },
    ],
    whenToUse: [
      'After search_elements narrows down to one candidate',
      'When you need to know parent/sibling structure for selector building',
      'Before writing CSS to know which classes/attrs exist',
    ],
    whenNotToUse: [
      'When you need all CSS rules (not just count) — use get_matching_css_rules',
      'When you need many elements — use search_elements first',
    ],
    relatedTools: ['search_elements', 'get_matching_css_rules', 'get_computed_styles', 'get_unique_selector', 'get_element_context'],
    handler: 'window.__aiPageTools.inspectElement',
  },

  get_page_summary: {
    group: 'dom',
    classification: 'discovery',
    purpose: 'Returns page-level metadata: URL, title, lang, viewport, body classes, landmarks present, the first 20 headings, and counts of forms/buttons/links/scripts/stylesheets.',
    inputSchema: {},
    outputSchema: {
      data: '{ url, title, lang, viewport:{w,h}, bodyClasses[], landmarks[], headings[], formsCount, buttonsCount, linksCount, scriptsCount, stylesheetsCount }',
    },
    examples: [
      { task: 'Orient yourself on an unfamiliar page before deciding next moves', input: {} },
    ],
    whenToUse: [
      'First call on a new page when you have no map of the content',
      'When the user gives a vague task ("clean up this site") and you need anchors',
    ],
    whenNotToUse: [
      'When you already know the area to inspect — go straight to search_elements',
    ],
    relatedTools: ['get_dom_outline', 'search_elements', 'list_panes'],
    handler: 'window.__aiPageTools.getPageSummary',
  },

  get_dom_outline: {
    group: 'dom',
    classification: 'discovery',
    purpose: 'Structural map of a subtree (NO text content). Each node: tag, selector, all attributes (capped), childrenCount, descendantLevels, descendantTags (tag→count for the whole subtree), textLength. `root` is REQUIRED — scope it. BFS, hard caps: maxDepth 3, 100 nodes; a level that would overflow the cap is dropped whole (never partial). descendantTags lets you see what is below a node without expanding it.',
    inputSchema: {
      root: { type: 'string', required: true, description: 'CSS selector for the subtree root (MANDATORY — call get_page_summary first to find landmarks if unsure)' },
      maxDepth: { type: 'number', required: false, default: 2, description: 'Levels of children below root (1-3)' },
      includeHidden: { type: 'boolean', required: false, default: false, description: 'Include hidden elements (display:none / visibility:hidden)' },
    },
    outputSchema: {
      data: 'Array<{ tag, selector, attributes?, attrTruncated?, childrenCount?, descendantLevels?, descendantTags?, textLength?, children?: [...] }>',
    },
    examples: [
      { task: 'Map the main content two levels deep', input: { root: 'main', maxDepth: 2 } },
      { task: 'Shallow outline of the header', input: { root: 'header', maxDepth: 1 } },
    ],
    whenToUse: [
      'You have a scope (root) and need its structural shape',
      'Deciding which child subtree to drill into next (descendantTags guides you)',
    ],
    whenNotToUse: [
      'You need text/styles of an element — use inspect_element',
      'You want to find specific elements — use search_elements',
      'You do not have a root yet — call get_page_summary first',
    ],
    relatedTools: ['get_page_summary', 'search_elements', 'inspect_element'],
    handler: 'window.__aiPageTools.getDomOutline',
  },

  get_computed_styles: {
    group: 'dom',
    classification: 'discovery',
    purpose: 'Returns selected computed CSS properties for one element. With no properties array, returns ~30 most useful defaults (display, position, color, font-*, margin, padding, etc.).',
    inputSchema: {
      selector: { type: 'string', required: true, description: 'CSS selector for the target element' },
      properties: { type: 'array', required: false, description: 'Explicit property names (CSS-prop-case, e.g. "background-color"). Omit for defaults.' },
    },
    outputSchema: {
      data: '{ [property]: value }',
    },
    examples: [
      { task: 'Find why text looks too small', input: { selector: '.product-title', properties: ['font-size', 'line-height', 'font-weight'] } },
      { task: 'Default style snapshot of a button', input: { selector: 'button.primary' } },
    ],
    whenToUse: [
      'When you need final resolved values, not the rules behind them',
      'Before writing override CSS to know the current state',
    ],
    whenNotToUse: [
      'When you need to know WHICH rule sets a value — use get_matching_css_rules',
      'When the element does not exist — call test_selector first',
    ],
    relatedTools: ['get_matching_css_rules', 'inspect_element', 'preview_css'],
    handler: 'window.__aiPageTools.getComputedStyles',
  },

  get_matching_css_rules: {
    group: 'dom',
    classification: 'discovery',
    purpose: 'CSS rules matching an element, sorted by specificity (highest first). Goal: find WHICH rule your override must beat — usually only the top 2-3 matter. WITHOUT `properties` each rule returns propertyNames only (no values); WITH `properties` it returns values for those props + otherProperties names, and drops rules touching none of them. Iterative narrowing: explore with no filter, then re-query with the property you care about.',
    inputSchema: {
      selector: { type: 'string', required: true, description: 'CSS selector for the target element' },
      properties: { type: 'array', required: false, description: 'Property names to get values for, e.g. ["color","background"]. Without this, only names are returned.' },
      limit: { type: 'number', required: false, default: 10, description: 'Max rules (hard cap 20)' },
      offset: { type: 'number', required: false, default: 0, description: 'Pagination offset' },
      includeInline: { type: 'boolean', required: false, default: true, description: 'Include the element inline style attribute (highest specificity)' },
    },
    outputSchema: {
      data: 'Array<{ selector, source, specificity:[a,b,c], inline?, (no filter: propertyNames[]) | (with filter: declarations{}, otherProperties[]) }>',
    },
    examples: [
      { task: 'Explore what defines a button (names only)', input: { selector: 'button.primary' } },
      { task: 'Get color/background values + which rule wins', input: { selector: 'button.primary', properties: ['color', 'background'] } },
    ],
    whenToUse: [
      'Your CSS override is being beaten — find the specificity to top (filter by the property)',
      'Debugging "why is this element styled this way" for a specific property',
    ],
    whenNotToUse: [
      'You only want final computed values — get_computed_styles is cheaper',
      'You have no specific element — search_elements + test_selector first',
    ],
    relatedTools: ['get_computed_styles', 'inspect_element', 'preview_css'],
    handler: 'window.__aiPageTools.getMatchingCssRules',
  },

  get_unique_selector: {
    group: 'dom',
    classification: 'discovery',
    purpose: 'Returns the most stable selector for an element using priority: data-testid > stable id > role+aria-label > semantic class > structural path. Includes a stabilityScore and warnings about dynamic identifiers.',
    inputSchema: {
      selector: { type: 'string', required: true, description: 'CSS selector that resolves to the target (typically a structural path you want to harden)' },
    },
    outputSchema: {
      data: '{ preferred: string, alternatives: string[], stabilityScore: 0..1, warnings: string[] }',
    },
    examples: [
      { task: 'Harden a nth-of-type path into a stable selector', input: { selector: 'main > section:nth-of-type(2) > button:nth-of-type(1)' } },
    ],
    whenToUse: [
      'After search_elements / inspect_element returned a structural path you do not trust',
      'Before saving a selector into a persistent action/rule',
      'When the user reports "selector broke after the page changed"',
    ],
    whenNotToUse: [
      'When the page has test attributes and the selector is already obviously stable',
    ],
    relatedTools: ['inspect_element', 'test_selector', 'search_elements'],
    handler: 'window.__aiPageTools.getUniqueSelector',
  },

  get_element_context: {
    group: 'dom',
    classification: 'discovery',
    purpose: 'Returns the nearest meaningful context around an element: nearest component wrapper (data-component attr or a class like product-card/list-item/…), enclosing section, enclosing form, and a linked list of siblings — each as { selector, tag, text, sameTag } so you can map a sibling to its selector AND text in one shot (sameTag flags peers of the same type, e.g. other product cards).',
    inputSchema: {
      selector: { type: 'string', required: true, description: 'CSS selector for the focus element' },
    },
    outputSchema: {
      data: '{ nearestComponent: string|null, section: string|null, form: string|null, siblings: Array<{ selector, tag, text, sameTag }> }',
    },
    examples: [
      { task: 'Discover the product card surrounding a price element', input: { selector: '.price' } },
    ],
    whenToUse: [
      'When you need scope for further queries (e.g. iterate siblings)',
      'When generating selectors that should be component-scoped',
      'When deciding whether an element belongs to a form/section',
    ],
    whenNotToUse: [
      'When you need full subtree — use get_dom_outline at the right root',
    ],
    relatedTools: ['inspect_element', 'get_dom_outline', 'get_unique_selector'],
    handler: 'window.__aiPageTools.getElementContext',
  },

  test_selector: {
    group: 'dom',
    classification: 'discovery',
    purpose: 'Returns the match count, visible-match count, and up to three example matches for a selector. Cheap sanity check before relying on a selector.',
    inputSchema: {
      selector: { type: 'string', required: true, description: 'CSS selector to test' },
      visibleOnly: { type: 'boolean', required: false, default: false, description: 'Sample the example matches from visible elements only (count + visibleCount are always reported regardless)' },
    },
    outputSchema: {
      data: '{ selector: string, count: number, visibleCount: number, examples: Array<{ text, path }> }',
    },
    examples: [
      { task: 'Confirm a selector matches exactly one element', input: { selector: '#checkout-button' } },
      { task: 'Spot hidden-vs-visible mismatch', input: { selector: '.modal' } },
    ],
    whenToUse: [
      'Before writing CSS/JS that depends on the selector being non-empty',
      'When debugging "my selector matches nothing"',
      'To distinguish "no matches" from "all matches are hidden"',
    ],
    whenNotToUse: [
      'When you need the actual elements — use search_elements',
    ],
    relatedTools: ['search_elements', 'inspect_element', 'get_unique_selector'],
    handler: 'window.__aiPageTools.testSelector',
  },

  run_js_probe: {
    group: 'dom',
    classification: 'action',
    purpose: 'Evaluate a snippet of JavaScript in the MAIN world. Optional readOnly mode blocks mutation patterns via a regex heuristic. Result is JSON-serialized (DOM elements become {__node, selector}). Timeout-protected.',
    inputSchema: {
      code: { type: 'string', required: true, description: 'JavaScript expression or statement to evaluate' },
      readOnly: { type: 'boolean', required: false, default: false, description: 'Block obvious mutations (innerHTML, classList.add, setAttribute, fetch, history.pushState, etc.) before execution' },
      timeout: { type: 'number', required: false, default: 2000, description: 'Wall-clock budget in ms. Note: synchronous infinite loops cannot be interrupted.' },
    },
    outputSchema: {
      data: 'Any (JSON-serialized — Element → {__node, selector}, Function → "[Function]")',
    },
    examples: [
      { task: 'Read document.title quickly', input: { code: 'document.title', readOnly: true } },
      { task: 'Compute viewport-relative position of an element', input: { code: 'document.querySelector("#hero").getBoundingClientRect()', readOnly: true } },
    ],
    whenToUse: [
      'When no existing tool covers the read you need (custom computation)',
      'For one-off introspection that would otherwise need multiple tool calls',
    ],
    whenNotToUse: [
      'When a dedicated tool exists (search_elements, get_computed_styles, etc.) — prefer it',
      'For DOM mutations meant to persist — use create_/update_ instead',
      'Anything that calls fetch / writes storage in production sessions',
    ],
    relatedTools: ['preview_js', 'observe_dom_changes', 'inspect_element'],
    handler: 'window.__aiPageTools.runJsProbe',
  },

  preview_css: {
    group: 'dom',
    classification: 'action',
    purpose: 'Inject a <style> tag into the live page (HEADLESS — not visible to the user as a pane) and report how many rules and elements were affected. Persists until reload; not auto-cleared.',
    inputSchema: {
      css: { type: 'string', required: true, description: 'CSS source. Multiple rules allowed.' },
    },
    outputSchema: {
      data: '{ applied: true, affectedElements: number, ruleCount: number }',
    },
    examples: [
      { task: 'Verify a layout fix before persisting it as a rule', input: { css: '.product-card { padding: 16px; border-radius: 8px; }' } },
    ],
    whenToUse: [
      'For headless visual testing before deciding to persist as a rule/pane',
      'To verify a selector hits the intended elements (affectedElements > 0)',
    ],
    whenNotToUse: [
      'When the user should SEE the change in a JustZix pane — use update_pane_code or create_css_pane',
      'For one-off CSS the user wants stored — go straight to create_rule',
    ],
    relatedTools: ['preview_js', 'test_selector', 'update_pane_code', 'create_rule'],
    handler: 'window.__aiPageTools.previewCss',
  },

  preview_js: {
    group: 'dom',
    classification: 'action',
    purpose: 'Run a JS snippet HEADLESS in the MAIN world while capturing console output, errors, and DOM mutations. No rollback in MVP — side effects persist until reload.',
    inputSchema: {
      code: { type: 'string', required: true, description: 'JavaScript to execute' },
      timeout: { type: 'number', required: false, default: 1000, description: 'Wall-clock budget in ms' },
    },
    outputSchema: {
      data: '{ success, returnValue, consoleLogs: [{level,args}], errors: [{message,stack}], domMutations: { added: string[], removed: string[], attrChanged: [{target,attr,old,new}] } }',
    },
    examples: [
      { task: 'Test a click-handler before wiring it into an action', input: { code: 'document.querySelector(".menu").classList.toggle("open"); "toggled"' } },
    ],
    whenToUse: [
      'When you want to know what a snippet does to the DOM before persisting',
      'To capture console output you cannot see otherwise',
    ],
    whenNotToUse: [
      'When the user needs to see the result — use update_pane_code or set_temp_pane_code',
      'For pure reads — run_js_probe with readOnly is safer',
    ],
    relatedTools: ['run_js_probe', 'preview_css', 'observe_dom_changes', 'update_pane_code'],
    handler: 'window.__aiPageTools.previewJs',
  },

  observe_dom_changes: {
    group: 'dom',
    classification: 'discovery',
    purpose: 'Trigger an action (click on a selector or eval code) and return the DOM diff observed within a timeout window: added / removed / attribute-changed elements. Our extension UI is filtered out of the diff.',
    inputSchema: {
      action: { type: 'string', required: false, default: 'click', description: 'What to trigger', enum: ['click', 'eval'] },
      selector: { type: 'string', required: false, description: 'Required when action=click' },
      code: { type: 'string', required: false, description: 'Required when action=eval' },
      timeout: { type: 'number', required: false, default: 1000, description: 'How long (ms) to keep watching after the action' },
    },
    outputSchema: {
      data: '{ added: Array<{selector,tag,text}>, removed: Array<{selector,tag}>, changed: Array<{selector,attribute,before,after}> }',
    },
    examples: [
      { task: 'Confirm clicking the menu trigger opens the panel', input: { action: 'click', selector: '.menu-trigger', timeout: 500 } },
    ],
    whenToUse: [
      'When you suspect a click reveals new elements and want to confirm',
      'For debugging "the button does nothing visible"',
    ],
    whenNotToUse: [
      'When you need timestamps for each change — use record_dom_timeline',
      'When you only want to read existing DOM — use search_elements',
    ],
    relatedTools: ['record_dom_timeline', 'preview_js', 'simulate_click'],
    handler: 'window.__aiPageTools.observeDomChanges',
  },

  record_dom_timeline: {
    group: 'dom',
    classification: 'discovery',
    purpose: 'Record DOM mutations over a short window as a sorted timeline. Goal: identify the PATTERN of change (meta.eventTypes counts + meta.targetSummary top-5 targets), not read every event — the events array is a sample (max 50). Use short windows (1-3s) after an action; scope with `root` when you know where changes happen.',
    inputSchema: {
      duration: { type: 'number', required: false, default: 1000, description: 'How long (ms) to observe (max 10000)' },
      throttle: { type: 'number', required: false, default: 200, description: 'Coalesce same-target/same-type events within this window (ms)' },
      filter: { type: 'string', required: false, default: 'all', description: 'Event type filter', enum: ['all', 'attrOnly', 'addOnly'] },
      root: { type: 'string', required: false, description: 'CSS selector to scope observation (default: whole document)' },
    },
    outputSchema: {
      data: 'Array<{ t, type:"added"|"removed"|"attrChanged"|"textChanged", target, details }> (sample, max 50); meta has eventTypes{} + targetSummary{}',
    },
    examples: [
      { task: 'What changes when the cart updates (scoped)', input: { duration: 2000, root: '#cart' } },
      { task: 'Record a 1s navigation transition', input: { duration: 1000 } },
    ],
    whenToUse: [
      'After a user action (simulate_click) to see the resulting changes over time',
      'Diagnosing spinner / lazy-load / SPA-route behavior',
    ],
    whenNotToUse: [
      'You only need a before/after diff — observe_dom_changes is simpler',
      'The page is static — likely overkill',
    ],
    relatedTools: ['observe_dom_changes', 'preview_js'],
    handler: 'window.__aiPageTools.recordDomTimeline',
  },

  // ════════════════════════════════════════════════════════════════════════
  // GROUP: tree (10) — folders / groups / rules (the rule library)
  // ════════════════════════════════════════════════════════════════════════

  list_all: {
    group: 'tree',
    classification: 'discovery',
    purpose: 'Returns COUNTS of every entity type in the library (folders, groups, rules, actions, action bars, CSS/JS panes, JS/output consoles). A quick size check before drilling in with list_structure / list_bars / list_panes.',
    inputSchema: {},
    outputSchema: {
      data: '{ summary: { folders, groups, rules, actions, actionBars, cssPanes, jsPanes, jsConsoles, outputConsoles }, note: string }',
    },
    examples: [
      { task: 'Quick overview of how much the user already has', input: {} },
    ],
    whenToUse: [
      'At session start to gauge library size before deciding how deep to inspect',
      'When the user asks "how much do I have" / "what is in my library"',
    ],
    whenNotToUse: [
      'When you need the actual tree — use list_structure',
      'When you need entity details — use read_* / list_bars / list_panes',
    ],
    relatedTools: ['list_structure', 'list_bars', 'list_actions', 'list_panes'],
    handler: '_aiToolListAll',
  },

  list_structure: {
    group: 'tree',
    classification: 'discovery',
    purpose: 'Returns the entire library tree (folders → groups → rules + persistent windows) with their IDs and names; folders also show their URL patterns. Single source for "what does the user already have". (A rule has both a css and a js field — there is no single "kind".)',
    inputSchema: {},
    outputSchema: {
      data: 'string — an indented text tree: FOLDER "..." id=… url=…, GRUPA "..." id=…, ZESTAW "..." id=…, plus persistent windows under each parent',
    },
    examples: [
      { task: 'Survey what the user already has saved before suggesting a new rule', input: {} },
    ],
    whenToUse: [
      'At session start when you need a map of the library',
      'Before create_* to avoid duplicates',
      'When the user references a rule by name — find its ID here',
    ],
    whenNotToUse: [
      'When you need full rule code — use read_rule after locating it',
      'When you only need bars — use list_bars',
    ],
    relatedTools: ['read_folder', 'read_group', 'read_rule', 'list_bars', 'list_panes'],
    handler: '_aiToolListStructure',
  },

  read_folder: {
    group: 'tree',
    classification: 'discovery',
    purpose: 'Returns metadata for one folder by ID: name, color, position, child group IDs.',
    inputSchema: {
      folderId: { type: 'string', required: true, description: 'Folder ID (e.g. "f-1729")' },
    },
    outputSchema: {
      data: '{ id, name, color, position, groupIds: string[] }',
    },
    examples: [
      { task: 'Inspect a folder before updating it', input: { folderId: 'f-1729' } },
    ],
    whenToUse: [
      'Before update_folder to confirm current state',
      'When list_structure surfaced the folder ID and you need details',
    ],
    whenNotToUse: [
      'When you also need rules and groups — list_structure is faster',
    ],
    relatedTools: ['list_structure', 'update_folder', 'read_group'],
    handler: '_aiToolReadEntity:folder',
  },

  read_group: {
    group: 'tree',
    classification: 'discovery',
    purpose: 'Returns metadata for one group by ID: name, enabled flag, parent folder ID, and child rule IDs with kind/enabled summary.',
    inputSchema: {
      groupId: { type: 'string', required: true, description: 'Group ID' },
    },
    outputSchema: {
      data: '{ id, name, enabled, folderId, rules: Array<{ id, name, kind, enabled }> }',
    },
    examples: [
      { task: 'List rules inside a group before editing one', input: { groupId: 'g-200' } },
    ],
    whenToUse: [
      'Before update_group / create_rule under this group',
      'After list_structure points to the group',
    ],
    whenNotToUse: [
      'When you need rule source code — use read_rule per rule',
    ],
    relatedTools: ['list_structure', 'update_group', 'read_rule', 'create_rule'],
    handler: '_aiToolReadEntity:group',
  },

  read_rule: {
    group: 'tree',
    classification: 'discovery',
    purpose: 'Returns full rule data by ID: name, kind (css/js), enabled flag, source code, matching URL patterns, group, and timestamps.',
    inputSchema: {
      ruleId: { type: 'string', required: true, description: 'Rule ID' },
    },
    outputSchema: {
      data: '{ id, name, kind: "css"|"js", enabled, code, matches: string[], groupId, createdAt, updatedAt }',
    },
    examples: [
      { task: 'Read the JS source of a saved rule before suggesting a change', input: { ruleId: 'r-512' } },
    ],
    whenToUse: [
      'Before update_rule — to know what to patch',
      'When user references a rule and you need to see what it actually does',
    ],
    whenNotToUse: [
      'When you only need names/IDs — read_group is enough',
    ],
    relatedTools: ['list_structure', 'read_group', 'update_rule', 'create_rule'],
    handler: '_aiToolReadEntity:rule',
  },

  create_folder: {
    group: 'tree',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Create a new folder in the library. Confirmation form appears inline. Note: creating a folder also auto-creates a default empty group inside it — meta returns autoCreatedDefaultGroup:true with the new groupId.',
    inputSchema: {
      name: { type: 'string', required: true, description: 'Folder display name' },
      color: { type: 'string', required: false, description: 'Hex color (e.g. "#4f8cff") for the folder accent' },
    },
    outputSchema: {
      data: '{ folderId: string, groupId: string }',
    },
    examples: [
      { task: 'Create a new folder for product-page tweaks', input: { name: 'Product page tweaks', color: '#4f8cff' } },
    ],
    whenToUse: [
      'When the user explicitly asks for a new folder',
      'Before create_group/create_rule that would not fit any existing folder',
    ],
    whenNotToUse: [
      'When an existing folder already fits — reuse it (check list_structure first)',
      'Do NOT call create_group right after — the default group is auto-created',
    ],
    relatedTools: ['list_structure', 'create_group', 'create_rule', 'update_folder'],
    handler: '_aiToolCreateEntity',
  },

  create_group: {
    group: 'tree',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Create a new group inside an existing folder. Confirmation form appears inline.',
    inputSchema: {
      folderId: { type: 'string', required: true, description: 'Parent folder ID' },
      name: { type: 'string', required: true, description: 'Group display name' },
      enabled: { type: 'boolean', required: false, default: true, description: 'Whether the group runs on matching pages' },
    },
    outputSchema: {
      data: '{ groupId: string }',
    },
    examples: [
      { task: 'Add a "Dark mode tweaks" group to an existing folder', input: { folderId: 'f-1729', name: 'Dark mode tweaks' } },
    ],
    whenToUse: [
      'When the user wants a new grouping inside an existing folder',
      'When organising several related rules under one toggle',
    ],
    whenNotToUse: [
      'Right after create_folder — the default group already exists; use the returned groupId',
    ],
    relatedTools: ['list_structure', 'create_folder', 'create_rule', 'update_group'],
    handler: '_aiToolCreateEntity',
  },

  create_rule: {
    group: 'tree',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Create a new rule inside a group. A rule has BOTH a css field and a js field (separate tabs) — set either or both — plus urlPatterns that decide where it applies. Rule starts enabled. Confirmation form appears inline.',
    inputSchema: {
      groupId: { type: 'string', required: true, description: 'Parent group ID (use the groupId returned by create_folder — do NOT create a separate group)' },
      name: { type: 'string', required: true, description: 'Rule display name' },
      css: { type: 'string', required: false, description: 'CSS injected on matching URLs (the rule\'s CSS tab)' },
      js: { type: 'string', required: false, description: 'JS injected on matching URLs (the rule\'s JS tab)' },
      urlPatterns: { type: 'array', required: false, description: 'URL match patterns, e.g. ["https://example.com/*"]. WITHOUT them the rule never matches — always set to the current site.' },
    },
    outputSchema: {
      data: '{ ruleId: string }',
    },
    examples: [
      { task: 'Persist a CSS tweak the user confirmed via preview_css', input: { groupId: 'g-200', name: 'Bigger product titles', css: '.product-title{font-size:1.4rem}', urlPatterns: ['https://example.com/*'] } },
      { task: 'Persist a JS snippet that runs on page load', input: { groupId: 'g-200', name: 'Hide banner on load', js: 'document.querySelector(".banner")?.remove()', urlPatterns: ['https://example.com/*'] } },
    ],
    whenToUse: [
      'After preview_css/preview_js the user accepts',
      'When the user explicitly asks to save a snippet',
    ],
    whenNotToUse: [
      'For one-off experimentation — preview_css/preview_js suffice',
      'When a similar rule exists — update_rule instead to avoid duplicates',
    ],
    relatedTools: ['preview_css', 'preview_js', 'update_rule', 'list_structure'],
    handler: '_aiToolCreateEntity',
  },

  update_folder: {
    group: 'tree',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Patch one or more fields of an existing folder. Confirmation form appears inline.',
    inputSchema: {
      folderId: { type: 'string', required: true, description: 'Folder ID to update' },
      patch: { type: 'object', required: true, description: 'Subset of { name, color, position } to apply' },
    },
    outputSchema: {
      data: '{ folderId: string, updated: string[] }',
    },
    examples: [
      { task: 'Rename a folder and recolor it', input: { folderId: 'f-1729', patch: { name: 'Product pages', color: '#10b981' } } },
    ],
    whenToUse: [
      'When the user explicitly asks to rename/recolor/reorder a folder',
    ],
    whenNotToUse: [
      'For creating new folders — use create_folder',
    ],
    relatedTools: ['read_folder', 'list_structure', 'create_folder'],
    handler: '_aiToolUpdateEntity:folder',
  },

  update_group: {
    group: 'tree',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Patch fields of an existing group. Confirmation form appears inline.',
    inputSchema: {
      groupId: { type: 'string', required: true, description: 'Group ID to update' },
      patch: { type: 'object', required: true, description: 'Subset of { name, enabled, folderId, position } to apply' },
    },
    outputSchema: {
      data: '{ groupId: string, updated: string[] }',
    },
    examples: [
      { task: 'Disable a group temporarily', input: { groupId: 'g-200', patch: { enabled: false } } },
      { task: 'Move a group to another folder', input: { groupId: 'g-200', patch: { folderId: 'f-2000' } } },
    ],
    whenToUse: [
      'When the user wants to toggle, rename, or relocate a group',
    ],
    whenNotToUse: [
      'For deleting — handled separately by the UI (no delete tool in v3.2.40)',
    ],
    relatedTools: ['read_group', 'list_structure', 'create_group'],
    handler: '_aiToolUpdateEntity:group',
  },

  update_rule: {
    group: 'tree',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Patch fields of an existing rule. Confirmation form appears inline.',
    inputSchema: {
      ruleId: { type: 'string', required: true, description: 'Rule ID to update' },
      patch: { type: 'object', required: true, description: 'Subset of { name, css, js, urlPatterns, enabled, active } to apply (css/js are the rule\'s two code tabs)' },
    },
    outputSchema: {
      data: '{ ruleId: string, updated: string[] }',
    },
    examples: [
      { task: 'Fix a typo in a rule\'s CSS', input: { ruleId: 'r-512', patch: { css: '.product-title{font-size:1.4rem;}' } } },
      { task: 'Add a JS snippet to an existing rule', input: { ruleId: 'r-512', patch: { js: 'console.log("hi")' } } },
      { task: 'Disable a rule', input: { ruleId: 'r-512', patch: { enabled: false } } },
    ],
    whenToUse: [
      'When refining an existing rule after preview validation',
      'When the user asks to enable/disable or rename a rule',
    ],
    whenNotToUse: [
      'For brand-new rules — use create_rule',
    ],
    relatedTools: ['read_rule', 'preview_css', 'preview_js', 'create_rule'],
    handler: '_aiToolUpdateEntity:rule',
  },

  // ════════════════════════════════════════════════════════════════════════
  // GROUP: bars (8) — action bars + actions
  // ════════════════════════════════════════════════════════════════════════

  list_bars: {
    group: 'bars',
    classification: 'discovery',
    purpose: 'Returns all action bars with id, name, parent (folder/group/rule), enabled/hidden flags, orientation, color, layoutMode, size, and actionOrder. Optionally filter to one parent with parentType + parentId.',
    inputSchema: {
      parentType: { type: 'string', required: false, description: 'Filter to bars under this parent type', enum: ['folder', 'group', 'rule'] },
      parentId: { type: 'string', required: false, description: 'Filter to bars under this parent id (use together with parentType)' },
    },
    outputSchema: {
      data: 'Array<{ id, name, parentType, parentId, orientation, color, enabled, hidden, layoutMode, size, actionOrder }>',
    },
    examples: [
      { task: 'Survey existing bars before suggesting a new one', input: {} },
    ],
    whenToUse: [
      'At session start when bars are relevant',
      'Before create_bar to avoid duplicates',
    ],
    whenNotToUse: [
      'When you need the actions themselves — use list_actions',
    ],
    relatedTools: ['list_actions', 'read_bar', 'create_bar', 'update_bar'],
    handler: '_aiToolListBars',
  },

  list_actions: {
    group: 'bars',
    classification: 'discovery',
    purpose: 'Returns every action across the library (optionally filtered by ruleId and/or barId), with id, label, type, parent rule/bar, code preview, and style fields.',
    inputSchema: {
      ruleId: { type: 'string', required: false, description: 'Limit results to actions belonging to one rule' },
      barId: { type: 'string', required: false, description: 'Limit results to one bar' },
    },
    outputSchema: {
      data: 'Array<{ id, ruleId, type, label, barId, color, layout, size }>',
    },
    examples: [
      { task: 'List actions inside a specific bar', input: { barId: 'b-300' } },
      { task: 'List every action everywhere', input: {} },
    ],
    whenToUse: [
      'Before create_action to see what already exists in the bar',
      'When the user asks "what buttons does this bar have"',
    ],
    whenNotToUse: [
      'When you need full action source code — use read_action',
    ],
    relatedTools: ['list_bars', 'read_action', 'create_action', 'update_action'],
    handler: '_aiToolListActions',
  },

  read_bar: {
    group: 'bars',
    classification: 'discovery',
    purpose: 'Returns full data for one action bar by ID: identity, style fields, parent, ordered action IDs.',
    inputSchema: {
      barId: { type: 'string', required: true, description: 'Bar ID' },
    },
    outputSchema: {
      data: '{ id, name, enabled, hidden, color, orientation, layoutMode, size, actionOrder: string[], parentType, parentId }',
    },
    examples: [
      { task: 'Inspect a bar before updating it', input: { barId: 'b-300' } },
    ],
    whenToUse: [
      'Before update_bar to know current state and field names',
      'When patching actionOrder you need the current order',
    ],
    whenNotToUse: [
      'When you also need every action\'s detail — use list_actions',
    ],
    relatedTools: ['list_bars', 'list_actions', 'update_bar'],
    handler: '_aiToolReadEntity:bar',
  },

  read_action: {
    group: 'bars',
    classification: 'discovery',
    purpose: 'Returns full data for one action by ID: identity (label, type, code), style (colors, layout, size), and behavior fields (options, min/max, defaultValue).',
    inputSchema: {
      actionId: { type: 'string', required: true, description: 'Action ID' },
    },
    outputSchema: {
      data: '{ id, barId, label, code, type, layout, size, color, colorText, colorBg, colorHover, options?, min?, max?, step?, defaultValue?, placeholder? }',
    },
    examples: [
      { task: 'Read an action\'s JS code before refactoring it', input: { actionId: 'a-1200' } },
    ],
    whenToUse: [
      'Before update_action to know what to patch',
      'When debugging an action\'s behavior',
    ],
    whenNotToUse: [
      'When you only need a summary — list_actions returns previews',
    ],
    relatedTools: ['list_actions', 'update_action', 'create_action'],
    handler: '_aiToolReadEntity:action',
  },

  create_bar: {
    group: 'bars',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Create a new action bar attached to a folder, group, or rule. Confirmation form appears inline. NOTE: the bar name is a short label capped at 6 characters and shown uppercased on the bar.',
    inputSchema: {
      name: { type: 'string', required: true, description: 'Bar label (capped at 6 characters, displayed uppercased)' },
      parentType: { type: 'string', required: true, description: 'What the bar attaches to', enum: ['folder', 'group', 'rule'] },
      parentId: { type: 'string', required: true, description: 'ID of the parent entity' },
      orientation: { type: 'string', required: false, default: 'horizontal', description: 'Bar layout direction', enum: ['horizontal', 'vertical'] },
      color: { type: 'string', required: false, description: 'Accent color (hex)' },
    },
    outputSchema: {
      data: '{ barId: string }',
    },
    examples: [
      { task: 'Add a horizontal bar to a group', input: { name: 'Tools', parentType: 'group', parentId: 'g-200', orientation: 'horizontal' } },
    ],
    whenToUse: [
      'When the user asks for a new bar attached to an existing entity',
    ],
    whenNotToUse: [
      'When a similar bar already exists — reuse via update_bar',
    ],
    relatedTools: ['list_bars', 'create_action', 'update_bar'],
    handler: '_aiToolCreateEntity',
  },

  create_action: {
    group: 'bars',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Create a new action (button/select/input/slider/textarea/toggle3) inside a rule, optionally placed on a bar. Confirmation form appears inline. NOTE: the label is capped at 8 characters by the backend.',
    inputSchema: {
      ruleId: { type: 'string', required: true, description: 'Parent rule the action belongs to' },
      label: { type: 'string', required: true, description: 'Visible action label (capped at 8 characters)' },
      type: { type: 'string', required: true, description: 'Action kind', enum: ['button', 'select', 'input', 'slider', 'textarea', 'toggle3'] },
      barId: { type: 'string', required: false, description: 'Bar to render the action on (must belong to the rule\'s folder/group/rule tree). Optional — can be assigned later in the editor.' },
      code: { type: 'string', required: false, description: 'JS code executed when the action fires' },
      options: { type: 'array', required: false, description: 'For type=select — option list' },
      min: { type: 'number', required: false, description: 'For type=slider — minimum' },
      max: { type: 'number', required: false, description: 'For type=slider — maximum' },
      step: { type: 'number', required: false, description: 'For type=slider — step' },
      placeholder: { type: 'string', required: false, description: 'For type=input/textarea — placeholder text' },
      defaultValue: { type: 'string', required: false, description: 'Initial value for input/slider/textarea' },
      color: { type: 'string', required: false, description: 'Main/active color (hex)' },
      colorText: { type: 'string', required: false, description: 'Text/active-text color (hex)' },
      colorBg: { type: 'string', required: false, description: 'For toggle3 — inactive background color (hex)' },
      colorHover: { type: 'string', required: false, description: 'For select/toggle3 — hover background color (hex)' },
      colorInactiveText: { type: 'string', required: false, description: 'For toggle3 — inactive-state text color (hex)' },
      colorLabel: { type: 'string', required: false, description: 'For slider — label color (hex)' },
      colorValue: { type: 'string', required: false, description: 'For slider — value color (hex)' },
    },
    outputSchema: {
      data: '{ actionId: string }',
    },
    examples: [
      { task: 'Add a font-size button to a bar', input: { ruleId: 'r-100', barId: 'b-300', label: 'A+', type: 'button', code: 'document.body.style.fontSize="120%"' } },
    ],
    whenToUse: [
      'When the user wants a new control in an existing bar',
    ],
    whenNotToUse: [
      'When the bar does not exist yet — create_bar first',
    ],
    relatedTools: ['list_actions', 'update_action', 'create_bar'],
    handler: '_aiToolCreateEntity',
  },

  update_bar: {
    group: 'bars',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Modify an existing action bar — identity, style, layout, parent reassignment. Confirmation form appears inline with grouped style fields.',
    inputSchema: {
      barId: { type: 'string', required: true, description: 'Bar ID to update' },
      patch: { type: 'object', required: true, description: 'Subset of bar fields to apply (see styleFields)' },
    },
    outputSchema: {
      data: '{ barId: string, updated: string[] }',
    },
    styleFields: {
      Identity: ['name', 'enabled', 'hidden'],
      Style: ['color', 'orientation'],
      Layout: ['layoutMode', 'size', 'actionOrder'],
      Hierarchy: ['parentType', 'parentId'],
    },
    examples: [
      { task: 'Hide a bar without deleting it', input: { barId: 'b-300', patch: { hidden: true } } },
      { task: 'Recolor and re-orient a bar', input: { barId: 'b-300', patch: { color: '#ef4444', orientation: 'vertical' } } },
    ],
    whenToUse: [
      'When the user asks to rename/recolor/reorient a bar',
      'When moving a bar to a different folder/group/rule',
    ],
    whenNotToUse: [
      'For creating new bars — use create_bar',
    ],
    relatedTools: ['read_bar', 'list_bars', 'update_action'],
    handler: '_aiToolUpdateEntity:bar',
  },

  update_action: {
    group: 'bars',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Modify an existing action — identity, style (colors), layout, behavior (options/min/max), hierarchy (bar/rule). Confirmation form appears inline with grouped style fields.',
    inputSchema: {
      actionId: { type: 'string', required: true, description: 'Action ID to update' },
      patch: { type: 'object', required: true, description: 'Subset of action fields to apply (see styleFields)' },
    },
    outputSchema: {
      data: '{ actionId: string, updated: string[] }',
    },
    styleFields: {
      Identity: ['label', 'code', 'type'],
      Style: ['color', 'colorText', 'colorBg', 'colorHover', 'colorInactiveText', 'colorLabel', 'colorValue'],
      Layout: ['layout', 'size'],
      Behavior: ['options', 'min', 'max', 'step', 'placeholder', 'defaultValue'],
      Hierarchy: ['barId', 'ruleId'],
    },
    examples: [
      { task: 'Change a button\'s code and color', input: { actionId: 'a-1200', patch: { code: 'document.documentElement.classList.toggle("dark")', colorBg: '#1f2937' } } },
    ],
    whenToUse: [
      'When the user asks to retitle, recolor, or rewire an existing action',
      'When tweaking slider bounds (min/max/step)',
    ],
    whenNotToUse: [
      'For brand-new actions — use create_action',
    ],
    relatedTools: ['read_action', 'list_actions', 'update_bar'],
    handler: '_aiToolUpdateEntity:action',
  },

  // ════════════════════════════════════════════════════════════════════════
  // GROUP: temp (4) — temporary user-visible windows (open_temp_pane + reads)
  // ════════════════════════════════════════════════════════════════════════

  open_temp_pane: {
    group: 'temp',
    classification: 'action',
    purpose: 'Open an EMPTY temporary (session-only) pane of the chosen type. OPTIONAL — set_temp_pane_code already opens the pane automatically if it is not open, so you do not need to call this first. Use it only to show a blank pane before you have code to write.',
    inputSchema: {
      type: { type: 'string', required: true, description: 'Pane type', enum: ['css', 'js'] },
    },
    outputSchema: {
      data: '{ opened: true, type: string }',
    },
    examples: [
      { task: 'Open an empty JS scratchpad before writing anything', input: { type: 'js' } },
    ],
    whenToUse: [
      'When you want to show the user a blank pane before you have code',
    ],
    whenNotToUse: [
      'When the code should persist across sessions — create_css_pane / create_js_pane',
      'For HEADLESS testing — use preview_css / preview_js',
    ],
    relatedTools: ['set_temp_pane_code', 'read_temp_pane', 'create_css_pane', 'create_js_pane', 'preview_css', 'preview_js'],
    handler: '_aiOpenTempPane',
  },

  set_temp_pane_code: {
    group: 'temp',
    classification: 'action',
    purpose: 'Write code into the temporary pane so the USER SEES IT. Opens the pane automatically if it is not open yet (no need to call open_temp_pane first). **Visibility Rule** — use this when the result is meant to be shown. For headless testing (no user-visible artifact) prefer preview_css / preview_js from the dom group.',
    inputSchema: {
      type: { type: 'string', required: true, description: 'Which temp pane to target', enum: ['css', 'js'] },
      code: { type: 'string', required: true, description: 'Code to set (overwrites the pane contents)' },
    },
    outputSchema: {
      data: '{ type: string, codeLength: number, applied: true }',
    },
    examples: [
      { task: 'Show a generated CSS snippet to the user in a CSS scratch pane', input: { type: 'css', code: 'body{background:#222}' } },
    ],
    whenToUse: [
      'When the user should SEE the code before deciding to save it',
      'During teaching/explanation flows where transparency matters',
    ],
    whenNotToUse: [
      'For headless verification — use preview_css / preview_js (no UI change)',
      'For persistent panes — use update_pane_code instead',
    ],
    relatedTools: ['open_temp_pane', 'read_temp_pane', 'preview_css', 'preview_js', 'update_pane_code'],
    handler: '_aiSetTempPaneCode',
  },

  read_temp_pane: {
    group: 'temp',
    classification: 'discovery',
    purpose: 'Read the current contents of a temporary pane (e.g. what the user has typed or what set_temp_pane_code wrote earlier).',
    inputSchema: {
      type: { type: 'string', required: true, description: 'Which temp pane to read', enum: ['css', 'js'] },
    },
    outputSchema: {
      data: '{ type, code, opened: boolean }',
    },
    examples: [
      { task: 'See whether the user already edited the JS scratchpad', input: { type: 'js' } },
    ],
    whenToUse: [
      'Before overwriting with set_temp_pane_code so you do not lose user edits',
      'When debugging "the user says my code is wrong" — read what they actually have',
    ],
    whenNotToUse: [
      'For persistent panes — use read_pane_code',
    ],
    relatedTools: ['set_temp_pane_code', 'open_temp_pane', 'read_pane_code'],
    handler: '_aiToolReadTempPane',
  },

  read_console: {
    group: 'temp',
    classification: 'action',
    purpose: 'Read the recent contents of the temp JS/Output console (last N captured lines: console.log, errors, output console results). NOTE: on first call it OPENS a temp Output Console window (a visible side effect) — hence classified as an action.',
    inputSchema: {},
    outputSchema: {
      data: 'string — a text summary of the last captured console lines (each prefixed with its level), or a note explaining the console is empty / only captures from now on',
    },
    examples: [
      { task: 'See what console.log emitted after the user ran their JS', input: {} },
    ],
    whenToUse: [
      'When debugging JS the user ran in the temp console',
      'After preview_js to cross-check captured logs (also returned inline)',
    ],
    whenNotToUse: [
      'When you need persistent console output — use read_js_console / read_output_console',
    ],
    relatedTools: ['read_js_console', 'read_output_console', 'preview_js'],
    handler: '_aiToolReadConsole',
  },

  // ════════════════════════════════════════════════════════════════════════
  // GROUP: panes (9) — persistent CSS/JS panes (user-visible windows)
  // ════════════════════════════════════════════════════════════════════════

  list_panes: {
    group: 'panes',
    classification: 'discovery',
    purpose: 'Returns every persistent pane (CSS, JS, JS console, output console) with id, name, kind, enabled flag, position, size, parent, and code length. Optionally filter by type, or by parent (parentType + parentId together).',
    inputSchema: {
      type: { type: 'string', required: false, description: 'Filter to one pane type', enum: ['css', 'js', 'jsConsole', 'output'] },
      parentType: { type: 'string', required: false, description: 'Filter to panes under this parent type (use together with parentId)', enum: ['folder', 'group', 'rule'] },
      parentId: { type: 'string', required: false, description: 'Filter to panes under this parent id (use together with parentType)' },
    },
    outputSchema: {
      data: 'Array<{ id, type: "cssPane"|"jsPane"|"jsConsole"|"outputConsole", name, enabled, parentType, parentId, color, position, size }>',
    },
    examples: [
      { task: 'Audit persistent panes before creating a new one', input: {} },
    ],
    whenToUse: [
      'Before create_css_pane / create_js_pane to avoid duplicates',
      'When the user asks "what panes do I have open"',
    ],
    whenNotToUse: [
      'When you need full code — use read_css_pane / read_js_pane / read_pane_code',
    ],
    relatedTools: ['read_css_pane', 'read_js_pane', 'read_pane_code', 'create_css_pane', 'create_js_pane'],
    handler: '_aiToolListPanes',
  },

  create_css_pane: {
    group: 'panes',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Create a persistent CSS pane attached to a folder/group/rule. Pane stays visible across sessions until closed. Confirmation form appears inline.',
    inputSchema: {
      name: { type: 'string', required: true, description: 'Pane title' },
      parentType: { type: 'string', required: true, description: 'What the pane attaches to', enum: ['folder', 'group', 'rule'] },
      parentId: { type: 'string', required: true, description: 'ID of the parent entity' },
      code: { type: 'string', required: false, description: 'Initial CSS contents' },
    },
    outputSchema: {
      data: '{ paneId: string }',
    },
    examples: [
      { task: 'Create a "Product page CSS" pane under a folder', input: { name: 'Product page CSS', parentType: 'folder', parentId: 'f-1729' } },
    ],
    whenToUse: [
      'When the user wants a permanent visible editor for a CSS chunk',
    ],
    whenNotToUse: [
      'For one-off testing — preview_css is headless and faster',
      'When the code should be a regular rule — use create_rule',
    ],
    relatedTools: ['list_panes', 'update_css_pane', 'update_pane_code', 'create_rule'],
    handler: '_aiToolCreateEntity',
  },

  create_js_pane: {
    group: 'panes',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Create a persistent JS pane attached to a folder/group/rule. Confirmation form appears inline.',
    inputSchema: {
      name: { type: 'string', required: true, description: 'Pane title' },
      parentType: { type: 'string', required: true, description: 'What the pane attaches to', enum: ['folder', 'group', 'rule'] },
      parentId: { type: 'string', required: true, description: 'ID of the parent entity' },
      code: { type: 'string', required: false, description: 'Initial JS contents' },
    },
    outputSchema: {
      data: '{ paneId: string }',
    },
    examples: [
      { task: 'Create a JS pane that holds a custom click handler', input: { name: 'Cart helpers', parentType: 'rule', parentId: 'r-512' } },
    ],
    whenToUse: [
      'When the user wants an editable visible JS workspace',
    ],
    whenNotToUse: [
      'For headless trial — use preview_js',
      'For long-lived logic — consider create_rule instead',
    ],
    relatedTools: ['list_panes', 'update_js_pane', 'update_pane_code', 'create_rule'],
    handler: '_aiToolCreateEntity',
  },

  read_css_pane: {
    group: 'panes',
    classification: 'discovery',
    purpose: 'Read identity and styling fields for a persistent CSS pane (without the code itself — use read_pane_code for that).',
    inputSchema: {
      paneId: { type: 'string', required: true, description: 'Pane ID' },
    },
    outputSchema: {
      data: '{ id, name, kind: "cssPane", enabled, color, headerBg, headerText, bodyBg, bodyText, runColor, position, size, parentType, parentId }',
    },
    examples: [
      { task: 'Inspect a pane\'s theme before restyling it', input: { paneId: 'p-css-700' } },
    ],
    whenToUse: [
      'Before update_css_pane to see current style fields',
    ],
    whenNotToUse: [
      'When you need the source code — use read_pane_code',
    ],
    relatedTools: ['read_pane_code', 'update_css_pane', 'list_panes'],
    handler: '_aiToolReadEntity:cssPane',
  },

  read_js_pane: {
    group: 'panes',
    classification: 'discovery',
    purpose: 'Read identity and styling fields for a persistent JS pane (without the code — use read_pane_code for that).',
    inputSchema: {
      paneId: { type: 'string', required: true, description: 'Pane ID' },
    },
    outputSchema: {
      data: '{ id, name, kind: "jsPane", enabled, color, headerBg, headerText, bodyBg, bodyText, runColor, position, size, parentType, parentId }',
    },
    examples: [
      { task: 'See current style fields before recoloring', input: { paneId: 'p-js-701' } },
    ],
    whenToUse: [
      'Before update_js_pane',
    ],
    whenNotToUse: [
      'When you need the code — use read_pane_code',
    ],
    relatedTools: ['read_pane_code', 'update_js_pane', 'list_panes'],
    handler: '_aiToolReadEntity:jsPane',
  },

  update_css_pane: {
    group: 'panes',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Modify identity/style/layout/hierarchy of a CSS pane. Confirmation form appears inline with grouped style fields. NOTE: changes to the code go through update_pane_code, not here.',
    inputSchema: {
      paneId: { type: 'string', required: true, description: 'Pane ID to update' },
      patch: { type: 'object', required: true, description: 'Subset of pane fields to apply (see styleFields)' },
    },
    outputSchema: {
      data: '{ paneId: string, updated: string[] }',
    },
    styleFields: {
      Identity: ['name', 'enabled'],
      Style: ['color', 'headerBg', 'headerText', 'bodyBg', 'bodyText', 'runColor'],
      Layout: ['position', 'size'],
      Hierarchy: ['parentType', 'parentId'],
    },
    examples: [
      { task: 'Restyle a pane header', input: { paneId: 'p-css-700', patch: { headerBg: '#1f2937', headerText: '#f9fafb' } } },
    ],
    whenToUse: [
      'When the user wants to rename, recolor, resize, or reparent a CSS pane',
    ],
    whenNotToUse: [
      'For code changes — use update_pane_code',
    ],
    relatedTools: ['read_css_pane', 'update_pane_code', 'list_panes'],
    handler: '_aiToolUpdateEntity:cssPane',
  },

  update_js_pane: {
    group: 'panes',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Modify identity/style/layout/hierarchy of a JS pane. Confirmation form appears inline with grouped style fields. NOTE: code changes go through update_pane_code.',
    inputSchema: {
      paneId: { type: 'string', required: true, description: 'Pane ID to update' },
      patch: { type: 'object', required: true, description: 'Subset of pane fields to apply (see styleFields)' },
    },
    outputSchema: {
      data: '{ paneId: string, updated: string[] }',
    },
    styleFields: {
      Identity: ['name', 'enabled'],
      Style: ['color', 'headerBg', 'headerText', 'bodyBg', 'bodyText', 'runColor'],
      Layout: ['position', 'size'],
      Hierarchy: ['parentType', 'parentId'],
    },
    examples: [
      { task: 'Move a JS pane to another group and rename it', input: { paneId: 'p-js-701', patch: { name: 'Cart helpers', parentType: 'group', parentId: 'g-200' } } },
    ],
    whenToUse: [
      'When restyling/relocating a JS pane',
    ],
    whenNotToUse: [
      'For code changes — use update_pane_code',
    ],
    relatedTools: ['read_js_pane', 'update_pane_code', 'list_panes'],
    handler: '_aiToolUpdateEntity:jsPane',
  },

  read_pane_code: {
    group: 'panes',
    classification: 'discovery',
    purpose: 'Read the current source code stored in a persistent CSS pane (cp_*) or JS pane (jp_*). Consoles have no editable code.',
    inputSchema: {
      paneId: { type: 'string', required: true, description: 'Pane ID' },
    },
    outputSchema: {
      data: '{ paneId, kind, code, length }',
    },
    examples: [
      { task: 'See the code inside a JS pane before patching it', input: { paneId: 'p-js-701' } },
    ],
    whenToUse: [
      'Before update_pane_code so you patch with full context',
      'When the user asks "what is in that pane"',
    ],
    whenNotToUse: [
      'For temp panes — use read_temp_pane',
    ],
    relatedTools: ['update_pane_code', 'read_css_pane', 'read_js_pane', 'read_temp_pane'],
    handler: '_aiToolReadPaneCode',
  },

  update_pane_code: {
    group: 'panes',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Replace the code inside a persistent CSS pane (cp_*) or JS pane (jp_*). Consoles have no editable code. Confirmation form appears inline so the user sees the diff before applying.',
    inputSchema: {
      paneId: { type: 'string', required: true, description: 'Pane ID' },
      code: { type: 'string', required: true, description: 'New code (overwrites the pane contents)' },
    },
    outputSchema: {
      data: 'string — a status note confirming the code was inserted into the pane (live textarea + sessionStorage), or an error note.',
    },
    examples: [
      { task: 'Replace the contents of a CSS pane with cleaned-up rules', input: { paneId: 'p-css-700', code: '.product-title{font-size:1.4rem;}' } },
    ],
    whenToUse: [
      'When the user accepted a code change in a persistent pane',
      'When refactoring a pane the user already has open',
    ],
    whenNotToUse: [
      'For headless verification — use preview_css / preview_js',
      'For temp panes — use set_temp_pane_code',
    ],
    relatedTools: ['read_pane_code', 'preview_css', 'preview_js', 'set_temp_pane_code'],
    handler: '_aiToolUpdatePaneCode',
  },

  // ════════════════════════════════════════════════════════════════════════
  // GROUP: consoles (6) — persistent JS / output consoles
  // ════════════════════════════════════════════════════════════════════════

  create_js_console: {
    group: 'consoles',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Create a persistent JS console (REPL-style pane) attached to a folder/group/rule. Confirmation form appears inline.',
    inputSchema: {
      name: { type: 'string', required: true, description: 'Console title' },
      parentType: { type: 'string', required: true, description: 'What the console attaches to', enum: ['folder', 'group', 'rule'] },
      parentId: { type: 'string', required: true, description: 'ID of the parent entity' },
    },
    outputSchema: {
      data: '{ paneId: string }',
    },
    examples: [
      { task: 'Create a debugging JS console for a rule', input: { name: 'Debug', parentType: 'rule', parentId: 'r-512' } },
    ],
    whenToUse: [
      'When the user wants a permanent visible REPL near their rule/group',
    ],
    whenNotToUse: [
      'For one-off output — temp pane (open_temp_pane kind=jsConsole) is lighter',
    ],
    relatedTools: ['list_panes', 'update_js_console', 'read_js_console', 'open_temp_pane'],
    handler: '_aiToolCreateEntity',
  },

  create_output_console: {
    group: 'consoles',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Create a persistent output console (read-only sink for JustZix logging) attached to a folder/group/rule. Confirmation form appears inline.',
    inputSchema: {
      name: { type: 'string', required: true, description: 'Console title' },
      parentType: { type: 'string', required: true, description: 'What the console attaches to', enum: ['folder', 'group', 'rule'] },
      parentId: { type: 'string', required: true, description: 'ID of the parent entity' },
    },
    outputSchema: {
      data: '{ paneId: string }',
    },
    examples: [
      { task: 'Create a visible output sink for a JS pane', input: { name: 'Output', parentType: 'group', parentId: 'g-200' } },
    ],
    whenToUse: [
      'When the user wants persistent visible output for one rule/group/folder',
    ],
    whenNotToUse: [
      'For ad-hoc inspection — open_temp_pane kind=outputConsole works for the session',
    ],
    relatedTools: ['list_panes', 'update_output_console', 'read_output_console', 'open_temp_pane'],
    handler: '_aiToolCreateEntity',
  },

  read_js_console: {
    group: 'consoles',
    classification: 'discovery',
    purpose: 'Read identity and style fields of a persistent JS console.',
    inputSchema: {
      paneId: { type: 'string', required: true, description: 'Pane ID' },
    },
    outputSchema: {
      data: '{ id, name, kind: "jsConsole", enabled, color, headerBg, headerText, bodyBg, bodyText, runColor, position, size, parentType, parentId }',
    },
    examples: [
      { task: 'Inspect a JS console before restyling', input: { paneId: 'p-jsc-800' } },
    ],
    whenToUse: [
      'Before update_js_console',
    ],
    whenNotToUse: [
      'For its captured log lines — use read_pane_code (for current input) or read_console (temp)',
    ],
    relatedTools: ['update_js_console', 'list_panes', 'read_pane_code'],
    handler: '_aiToolReadEntity:jsConsole',
  },

  read_output_console: {
    group: 'consoles',
    classification: 'discovery',
    purpose: 'Read identity and style fields of a persistent output console.',
    inputSchema: {
      paneId: { type: 'string', required: true, description: 'Pane ID' },
    },
    outputSchema: {
      data: '{ id, name, kind: "outputConsole", enabled, color, headerBg, headerText, bodyBg, bodyText, runColor, position, size, parentType, parentId }',
    },
    examples: [
      { task: 'Inspect an output console before restyling', input: { paneId: 'p-out-801' } },
    ],
    whenToUse: [
      'Before update_output_console',
    ],
    whenNotToUse: [
      'For its captured output — use read_pane_code',
    ],
    relatedTools: ['update_output_console', 'list_panes', 'read_pane_code'],
    handler: '_aiToolReadEntity:outputConsole',
  },

  update_js_console: {
    group: 'consoles',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Modify identity/style/layout/hierarchy of a persistent JS console. Confirmation form appears inline with grouped style fields. Code changes go through update_pane_code.',
    inputSchema: {
      paneId: { type: 'string', required: true, description: 'Pane ID' },
      patch: { type: 'object', required: true, description: 'Subset of pane fields to apply (see styleFields)' },
    },
    outputSchema: {
      data: '{ paneId: string, updated: string[] }',
    },
    styleFields: {
      Identity: ['name', 'enabled'],
      Style: ['color', 'headerBg', 'headerText', 'bodyBg', 'bodyText', 'runColor'],
      Layout: ['position', 'size'],
      Hierarchy: ['parentType', 'parentId'],
    },
    examples: [
      { task: 'Rename and resize a JS console', input: { paneId: 'p-jsc-800', patch: { name: 'Cart REPL', size: { width: 480, height: 280 } } } },
    ],
    whenToUse: [
      'When the user wants to restyle/relocate a persistent JS console',
    ],
    whenNotToUse: [
      'For code changes — use update_pane_code',
    ],
    relatedTools: ['read_js_console', 'update_pane_code', 'list_panes'],
    handler: '_aiToolUpdateEntity:jsConsole',
  },

  update_output_console: {
    group: 'consoles',
    classification: 'action',
    requiresConfirmation: true,
    purpose: 'Modify identity/style/layout/hierarchy of a persistent output console. Confirmation form appears inline with grouped style fields.',
    inputSchema: {
      paneId: { type: 'string', required: true, description: 'Pane ID' },
      patch: { type: 'object', required: true, description: 'Subset of pane fields to apply (see styleFields)' },
    },
    outputSchema: {
      data: '{ paneId: string, updated: string[] }',
    },
    styleFields: {
      Identity: ['name', 'enabled'],
      Style: ['color', 'headerBg', 'headerText', 'bodyBg', 'bodyText', 'runColor'],
      Layout: ['position', 'size'],
      Hierarchy: ['parentType', 'parentId'],
    },
    examples: [
      { task: 'Move an output console to another rule', input: { paneId: 'p-out-801', patch: { parentType: 'rule', parentId: 'r-512' } } },
    ],
    whenToUse: [
      'When restyling/relocating a persistent output console',
    ],
    whenNotToUse: [
      'For code changes — use update_pane_code',
    ],
    relatedTools: ['read_output_console', 'update_pane_code', 'list_panes'],
    handler: '_aiToolUpdateEntity:outputConsole',
  },

  // ════════════════════════════════════════════════════════════════════════
  // GROUP: interaction (6) — synthetic user input on the page
  // ════════════════════════════════════════════════════════════════════════
  // NOTE for all interaction tools: synthetic events have isTrusted:false.
  // Security-aware SPAs may reject them. Side effects are REAL — form submits
  // fire real network requests, clicks may navigate the page, etc.

  simulate_click: {
    group: 'interaction',
    classification: 'action',
    purpose: 'Dispatch a synthetic MouseEvent("click") on a selector, optionally with a modifier key. Bubbles by default. Returns whether default was prevented. isTrusted is false — some apps reject synthetic clicks.',
    inputSchema: {
      selector: { type: 'string', required: true, description: 'CSS selector for the target' },
      modifier: { type: 'string', required: false, description: 'Single modifier to hold', enum: ['ctrl', 'shift', 'alt', 'meta'] },
    },
    outputSchema: {
      data: '{ dispatched: boolean, target: string }',
    },
    examples: [
      { task: 'Click the cart icon to open the side panel', input: { selector: '.cart-icon' } },
      { task: 'Ctrl-click to open a link in a new tab (browser dependent)', input: { selector: 'a.product-link', modifier: 'ctrl' } },
    ],
    whenToUse: [
      'To trigger UI flows without real user input',
      'In combination with observe_dom_changes to verify a flow',
    ],
    whenNotToUse: [
      'When the app validates isTrusted (e.g. payment SDKs) — synthetic clicks will fail',
      'For navigation tests that need real history — open_temp_pane / preview_js are not substitutes',
    ],
    relatedTools: ['observe_dom_changes', 'simulate_hover', 'simulate_keypress'],
    handler: 'window.__aiPageTools.simulateClick',
  },

  simulate_hover: {
    group: 'interaction',
    classification: 'action',
    purpose: 'Dispatch mouseenter + mouseover + mousemove on the selector to trigger hover-only states (tooltips, menus). isTrusted is false.',
    inputSchema: {
      selector: { type: 'string', required: true, description: 'CSS selector for the target' },
    },
    outputSchema: {
      data: '{ dispatched: boolean, target: string }',
    },
    examples: [
      { task: 'Surface a tooltip before screenshotting it', input: { selector: '.help-icon' } },
    ],
    whenToUse: [
      'When a UI element shows extra content only on hover',
      'Before screenshot_element to capture hover state',
    ],
    whenNotToUse: [
      'When the app relies on real mousemove sequences (e.g. drag handles) — single dispatch is insufficient',
    ],
    relatedTools: ['simulate_click', 'screenshot_element', 'observe_dom_changes'],
    handler: 'window.__aiPageTools.simulateHover',
  },

  simulate_scroll: {
    group: 'interaction',
    classification: 'action',
    purpose: 'Scroll the window to coordinates OR scroll an element into view via scrollIntoView. Smooth by default.',
    inputSchema: {
      selector: { type: 'string', required: false, description: 'If provided, scrollIntoView is used' },
      x: { type: 'number', required: false, description: 'Window scroll target X (used when selector is omitted)' },
      y: { type: 'number', required: false, description: 'Window scroll target Y' },
      smooth: { type: 'boolean', required: false, default: true, description: 'Smooth-scroll behavior' },
    },
    outputSchema: {
      data: '{ scrolled: boolean, position: {x,y} }',
    },
    examples: [
      { task: 'Scroll a CTA button into view', input: { selector: '#main-cta' } },
      { task: 'Jump to the page bottom', input: { y: 99999 } },
    ],
    whenToUse: [
      'Before screenshot_page when the target is below the fold',
      'To verify intersection-observer / lazy-load behavior in concert with record_dom_timeline',
    ],
    whenNotToUse: [
      'When the page disables programmatic scrolling — no-op without error',
    ],
    relatedTools: ['screenshot_page', 'record_dom_timeline', 'observe_dom_changes'],
    handler: 'window.__aiPageTools.simulateScroll',
  },

  simulate_keypress: {
    group: 'interaction',
    classification: 'action',
    purpose: 'Focus an element (optional) and dispatch KeyboardEvent keydown + keyup with optional modifiers. For single printable keys on inputs/textareas, an "input" event is also emitted with the character.',
    inputSchema: {
      selector: { type: 'string', required: false, description: 'Element to focus before the keypress (default: document.activeElement or body)' },
      key: { type: 'string', required: true, description: 'Key name (e.g. "Enter", "Tab", "Escape", "ArrowDown", "a")' },
      modifiers: { type: 'array', required: false, description: 'Subset of ["ctrl","shift","alt","meta"]' },
    },
    outputSchema: {
      data: '{ dispatched: boolean, target: string, key: string, modifiers: string[] }',
    },
    examples: [
      { task: 'Submit a form via Enter', input: { selector: 'form input[name=q]', key: 'Enter' } },
      { task: 'Close a modal with Escape', input: { key: 'Escape' } },
    ],
    whenToUse: [
      'To trigger keyboard shortcuts in widgets that only react to keys',
      'After simulate_type to commit input (Enter) or move (Tab)',
    ],
    whenNotToUse: [
      'For typing several characters — simulate_type sets the value in one call',
      'For trusted-only paths (e.g. paste detection) — synthetic events are isTrusted:false',
    ],
    relatedTools: ['simulate_type', 'simulate_click', 'observe_dom_changes'],
    handler: 'window.__aiPageTools.simulateKeypress',
  },

  simulate_type: {
    group: 'interaction',
    classification: 'action',
    purpose: 'Set the value of an <input>/<textarea> (using the native setter so React/Vue see the change) or the textContent of a contenteditable, then fire input + change events. Optionally clears existing value first.',
    inputSchema: {
      selector: { type: 'string', required: true, description: 'CSS selector for the field (input / textarea / contenteditable)' },
      text: { type: 'string', required: true, description: 'Text to type' },
      clear: { type: 'boolean', required: false, default: true, description: 'Whether to clear the current value before typing' },
    },
    outputSchema: {
      data: '{ typed: boolean, value: string, target: string }',
    },
    examples: [
      { task: 'Fill a search input with a query', input: { selector: 'input[name=q]', text: 'shoes' } },
      { task: 'Append to existing value', input: { selector: 'textarea#notes', text: ' (added by AI)', clear: false } },
    ],
    whenToUse: [
      'To fill a single field as part of a flow before simulate_click on submit',
      'When React-controlled inputs need the native value setter',
    ],
    whenNotToUse: [
      'For many fields at once — simulate_form_fill is one call',
      'When the field is non-standard (custom web component) — may need bespoke handling',
    ],
    relatedTools: ['simulate_form_fill', 'simulate_keypress', 'simulate_click'],
    handler: 'window.__aiPageTools.simulateType',
  },

  simulate_form_fill: {
    group: 'interaction',
    classification: 'action',
    purpose: 'Fill multiple fields of a <form> by name → value map. Handles inputs, textareas, selects, checkboxes (boolean), and radio groups (value selects the matching option). Each field fires input + change.',
    inputSchema: {
      formSelector: { type: 'string', required: true, description: 'CSS selector for the form' },
      values: { type: 'object', required: true, description: 'Map of [name] → value. For radio groups, value selects matching option.' },
    },
    outputSchema: {
      data: '{ filled: Array<{name, value}>, skipped: Array<{name, reason}> }',
    },
    examples: [
      { task: 'Fill out a sign-up form', input: { formSelector: 'form#signup', values: { email: 'user@example.com', password: 'Secret1!', newsletter: true } } },
    ],
    whenToUse: [
      'When the user wants several fields filled in one shot',
      'In test/repro flows that need a known form state',
    ],
    whenNotToUse: [
      'When the form uses custom widgets bound by JS — fields may report "skipped"',
      'Real submission requires a follow-up simulate_click on the submit button',
    ],
    relatedTools: ['simulate_type', 'simulate_click', 'observe_dom_changes'],
    handler: 'window.__aiPageTools.simulateFormFill',
  },

  // ════════════════════════════════════════════════════════════════════════
  // GROUP: ui (1) — inline user-input request
  // ════════════════════════════════════════════════════════════════════════

  request_user_input: {
    group: 'ui',
    classification: 'action',
    purpose: 'Show the user an inline form (or simple prompt) and wait for the response. This IS the confirmation mechanism — it never requires confirmation itself. Every form ALSO supports free-text reply: result includes a `text` field for the user\'s unstructured answer.',
    inputSchema: {
      title: { type: 'string', required: false, description: 'Short heading for the form' },
      description: { type: 'string', required: false, description: 'One-paragraph context shown above the fields' },
      fields: { type: 'array', required: false, description: 'Array of field specs: { name, label, type: "text"|"number"|"select"|"checkbox", options?, default? }' },
      submitLabel: { type: 'string', required: false, default: 'OK', description: 'Label on the submit button' },
      cancelLabel: { type: 'string', required: false, default: 'Cancel', description: 'Label on the cancel button (omit to hide)' },
    },
    outputSchema: {
      data: '{ action: "submit"|"cancel", values: { [name]: any }, text?: string }',
    },
    examples: [
      { task: 'Ask the user to pick a color for a new bar', input: { title: 'Pick color', fields: [{ name: 'color', label: 'Accent color', type: 'select', options: ['blue', 'green', 'red'] }] } },
      { task: 'Ask free-form follow-up question', input: { title: 'Anything else to change?' } },
    ],
    whenToUse: [
      'When a decision cannot be reasonably inferred from context',
      'For destructive/high-impact actions before committing',
      'When the user needs to choose between multiple plausible defaults',
    ],
    whenNotToUse: [
      'For obvious yes-paths — do not gate routine work behind extra forms',
      'When the user has already stated the preference in the conversation',
    ],
    relatedTools: ['create_rule', 'create_bar', 'update_bar', 'update_pane_code'],
    handler: '_aiToolRequestUserInput',
  },

  // ════════════════════════════════════════════════════════════════════════
  // GROUP: vision (2) — screenshot tools (require vision-capable model)
  // ════════════════════════════════════════════════════════════════════════
  // NOTE for both: requires a vision-capable provider/model. Cooldown 500 ms
  // between captures. Buffer holds at most 3 single-use screenshots — older
  // ones are dropped automatically.

  screenshot_page: {
    group: 'vision',
    classification: 'action',
    purpose: 'Capture the visible viewport as a single-use PNG, attached to the next model turn. Requires a vision-capable model. Cooldown 500 ms; max 3 in-buffer.',
    inputSchema: {},
    outputSchema: {
      data: 'string — a status note confirming the screenshot was buffered (with its id and viewport dimensions) for attachment to the next model turn, or an error/cooldown note',
    },
    examples: [
      { task: 'Show the AI exactly what the user is seeing right now', input: {} },
      { task: 'Capture after scroll to inspect below-the-fold area', input: {} },
    ],
    whenToUse: [
      'When verbal/DOM description cannot capture the visual issue',
      'After simulate_scroll / simulate_click to verify the new state',
      'When the user shares a layout problem the screenshot can disambiguate',
    ],
    whenNotToUse: [
      'When the current model has no vision capability — capture wastes the buffer',
      'For programmatic checks — search_elements + get_computed_styles are deterministic',
    ],
    relatedTools: ['screenshot_element', 'simulate_scroll', 'observe_dom_changes'],
    handler: '_aiToolScreenshotPage',
  },

  screenshot_element: {
    group: 'vision',
    classification: 'action',
    purpose: 'Capture only the element\'s bounding rect (plus optional padding) as a single-use PNG. Requires a vision-capable model. Cooldown 500 ms; max 3 in-buffer. The element must be in the viewport — call simulate_scroll first if it is not.',
    inputSchema: {
      selector: { type: 'string', required: true, description: 'CSS selector for the element to crop to' },
      padding: { type: 'number', required: false, default: 0, description: 'Extra pixels around the element to include in the crop' },
    },
    outputSchema: {
      data: '{ captured: true, screenshotId: string, rect: {x,y,width,height}, bufferSize: number }',
    },
    examples: [
      { task: 'Capture just a product card', input: { selector: '.product-card', padding: 8 } },
    ],
    whenToUse: [
      'When the visual issue is localized to one element',
      'To save vision tokens vs full-page captures',
      'After simulate_hover to capture a hover state',
    ],
    whenNotToUse: [
      'When the layout question is page-wide — screenshot_page is better',
      'When the element is off-screen — scroll first',
    ],
    relatedTools: ['screenshot_page', 'simulate_scroll', 'simulate_hover'],
    handler: '_aiToolScreenshotElement',
  },

};
