PATCHES the stored rules field-by-field: an omitted setting (frequency / priority / waitSeconds / startIfNotComplete) keeps its stored value — including one inherited from the forked version — so to turn a setting off, send it explicitly (e.g. startIfNotComplete: false). when, when present, fully replaces the condition list. null clears the rules entirely (content stops auto-starting). Which settings and which when condition types the CONTENT TYPE supports varies (e.g. no priority on a launcher, attribute/segment-only targeting on an announcement) — unsupported ones are rejected per type.
Announcement versions only: the "announcement time" — the feed hides the announcement until this instant passes, and orders the feed by it (newest first). ISO date or datetime WITH timezone. null = clear (publish stamps the publish time instead). A future value defers visibility; the value carries across version forks.
When this version FIRST went live (ISO). Non-null means the version is frozen: it can never be edited again — not even after unpublishing — edit by forking (by forking). null means it never went live — OR its first publish predates this stamp (older versions were never backfilled), so on old data null is not proof it never shipped. This is history, not live state: for "is it live NOW, where" read the content's environments[]; for who published what when, the publish history (available through the MCP).
While when matches, on-screen content of this version is hidden — the session is SUSPENDED, not ended: when the conditions stop matching the same session reappears at the same step. Use start-rule conditions to keep content from starting; use hide rules to blank it in specific places (e.g. a settings page).
Decompiled type-specific body for non-flow content — the same six per-type shapes as the write data (that schema is the field dictionary), fully populated on read. Present only when the data expand is requested; a flow has no data — its body is steps.
Per-locale translation status — present only when the localizations expand is requested on the content-versions endpoints. One entry per non-default project locale, including locales this version was never translated into.
Announcement versions only: the "announcement time" gating feed visibility and ordering. Absent on other content types and until set (publish stamps it when the author left it null, so published announcements always carry one).
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Usertour documention","colors":{"primary":"#0D9373","light":"#07C983","dark":"#0D9373"},"logo":{"light":"https://mintcdn.com/usertour/fGy2waOqk2YYQ2QK/logo-dark.svg?fit=max\u0026auto=format\u0026n=fGy2waOqk2YYQ2QK\u0026q=85\u0026s=841070ff620cff55401ec69cfc514e27","dark":"https://mintcdn.com/usertour/fGy2waOqk2YYQ2QK/logo-light.svg?fit=max\u0026auto=format\u0026n=fGy2waOqk2YYQ2QK\u0026q=85\u0026s=28f06f1ac3d117bd77837574e801ed7b","href":"https://www.usertour.io/"},"favicon":"https://mintcdn.com/usertour/nYBy6MD0LzNhBLol/images/favicon.png?fit=max\u0026auto=format\u0026n=nYBy6MD0LzNhBLol\u0026q=85\u0026s=2d933e312d87107d569af0615a00225b","api":{"playground":{"display":"simple"}},"navbar":{"links":[{"href":"mailto:support@usertour.io","label":"Support"}]},"footer":{"socials":{"x":"https://x.com/usertourio","github":"https://github.com/usertour/usertour","discord":"https://discord.gg/WPVJPX8fJh"}},"styling":{"latex":false},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"usertour","hasAudioNarration":false,"actualSubdomain":"usertour","customDomains":["docs.usertour.io"],"gitSource":{"type":"github","owner":"usertour","repo":"docs","deployBranch":"main","contentDirectory":"","isPrivate":true,"mountPath":"","label":""},"entitlements":{},"buildId":"6af2be424ef979744de2e2db","clientVersion":"0.0.4094","searchFilterCounts":[{"language":"default","tag":"API Reference","count":92},{"language":"default","tag":"Endpoints","count":79},{"language":"default","tag":"Documentation","count":49},{"language":"default","tag":"API Reference (v1)","count":37},{"language":"default","tag":"How-to Guides","count":31},{"language":"default","tag":"Usertour.js Reference","count":31},{"language":"default","tag":"Self-Hosting","count":20},{"language":"default","tag":"Content","count":15},{"language":"default","tag":"Users","count":14},{"language":"default","tag":"Companies","count":13}]},"assistantConfig":{},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-rbac-roles","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"rbac-expansion","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-metadata","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-serve","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"live-agents","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[],"page":{"pagePath":"api-reference/content-versions/update-a-content-version","slug":"/api-reference/content-versions/update-a-content-version","pageMetadata":{"title":"Update a content version","description":"Write steps, start/hide rules, themeId, or type-specific data to a draft version.","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json patch /v2/projects/{projectId}/content/{contentId}/versions/{id}","href":"/api-reference/content-versions/update-a-content-version","autogeneratedByOpenApi":true},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"Write steps, start/hide rules, themeId, or type-specific data to a draft version.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){return _jsx(_Fragment,{})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Update a content version","description":"Write steps, start/hide rules, themeId, or type-specific data to a draft version.","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json patch /v2/projects/{projectId}/content/{contentId}/versions/{id}","href":"/api-reference/content-versions/update-a-content-version","autogeneratedByOpenApi":true}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[],"codeExamples":{}},"jsLinks":[],"openApiReferenceData":{"operation":{"operationId":"ApiContentVersionsController_update","summary":"Update a content version","description":"Write steps, start/hide rules, themeId, or type-specific data to a draft version.","parameters":["f609c228-8555-471b-aa0c-bfff42961a25","61aa91c3-5c4c-4c1a-96a4-8f194e154183","51bdad26-240f-4dbd-ba08-1addf349c1b5"],"requestBody":"f282e250-4c23-4e44-8d83-2a7f7dcf80a1","responses":{"200":"3c423bb5-6430-407e-836f-bfdeda54f538","400":"824a67fa-71bd-4a90-b850-86610eef75a1","401":"688e5ec4-d5d5-43e8-8213-29b3d8e5c069","403":"f006b1c3-736f-4f14-a8d4-baeb00161fce","404":"141ac6ec-b62d-4695-a706-216d131d2127","409":"a168bc15-666e-4bd4-a154-fcd38442d008","429":"c143e5cc-ab03-4988-af15-efb61ab472f5"},"tags":["Content versions"],"security":["c10e290b-8d50-4a5f-859e-4b77d9e48be0"],"method":"patch","type":"path","servers":["23c31da0-5340-453a-ad80-9d71f43aa3ab"],"path":"/v2/projects/{projectId}/content/{contentId}/versions/{id}","baseUrlOptions":["https://api.usertour.io"],"title":"Update a content version","codeSamples":[],"requiredOnlyExamples":false,"requestExampleType":"generated","responseExampleType":"generated"},"dependencies":{"parameters":{"f609c228-8555-471b-aa0c-bfff42961a25":{"name":"id","required":true,"in":"path","description":"Content version ID","schema":{"type":"string","isRequired":true,"description":"Content version ID","uniqueKey":"","typeLabel":"string"}},"61aa91c3-5c4c-4c1a-96a4-8f194e154183":{"name":"contentId","required":true,"in":"path","description":"Content ID","schema":{"type":"string","isRequired":true,"description":"Content ID","uniqueKey":"","typeLabel":"string"}},"51bdad26-240f-4dbd-ba08-1addf349c1b5":{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string","isRequired":true,"description":"Project ID","uniqueKey":"","typeLabel":"string"}}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"steps":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].name","typeLabel":"string"},"type":{"type":"string","enum":["tooltip","modal","hidden","bubble"],"description":"Step kind. Only `tooltip` anchors to a target element; the rest are page-level.","isRequired":true,"uniqueKey":"steps.items.[INDEX].type","typeLabel":"enum\u003cstring>"},"id":{"description":"Primary id of an existing step to update in place. It is regenerated when you fork a version, so for edits that must survive a fork prefer `cvid`.","type":"string","uniqueKey":"steps.items.[INDEX].id","typeLabel":"string"},"cvid":{"description":"Stable step handle that survives forking: echo it to update an existing step in place without re-reading new ids. A step is matched by `id` first, then `cvid`; omit both to create a new step.","type":"string","uniqueKey":"steps.items.[INDEX].cvid","typeLabel":"string"},"key":{"description":"Optional author-chosen handle for wiring \"go to step\" actions within this request (unique among the steps you send). A button action can set `step` to this key to target this step even before it exists. Write-only — not stored, not returned on read.","type":"string","uniqueKey":"steps.items.[INDEX].key","typeLabel":"string"},"sequence":{"type":"number","uniqueKey":"steps.items.[INDEX].sequence","typeLabel":"number"},"themeId":{"oneOf":[{"type":"string","description":"Per-step theme override. Omit to keep the current value; set to a theme id (from the themes list) to override; set to null to clear and inherit the flow theme.","uniqueKey":"steps.items.[INDEX].themeId.oneOf.0","typeLabel":"string"},{"type":"null","description":"Per-step theme override. Omit to keep the current value; set to a theme id (from the themes list) to override; set to null to clear and inherit the flow theme.","uniqueKey":"steps.items.[INDEX].themeId.oneOf.1","typeLabel":"null"}],"uniqueKey":"steps.items.[INDEX].themeId","typeLabel":"string | null"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].target","typeLabel":"object"},"placement":{"oneOf":[{"type":"object","description":"Two placement shapes, by step kind: a TOOLTIP (anchored to a `target`) uses `{ side, align, sideOffset?, alignOffset?, alignType? }` positioned relative to the element; a MODAL uses `{ position, offsetX?, offsetY? }` on a 9-cell viewport grid (e.g. `\"center\"`). Both may set `backdrop`; `blockTarget` is TOOLTIP-ONLY (a modal already covers the page) and additionally requires `backdrop: true` — it makes the backdrop swallow clicks on the highlighted element. A BUBBLE step is positioned by its THEME's bubble placement, so the only key it accepts here is `{ backdrop }` (positional keys are rejected — move the bubble by changing the theme); a HIDDEN step renders no UI and rejects `placement` entirely.","properties":{"side":{"description":"Which side of the target the tooltip sits on. OMIT side+align to auto-position (the tooltip picks a spot and flips to avoid the viewport edge — best when you can't see the element). Setting side (or align) pins that direction: the tooltip renders exactly there and no longer auto-flips, so only pin when the element is not near a screen edge. When READING a step back, check `alignType` first: under `auto` the side/align you see are just the auto starting position, not a pin.","type":"string","enum":["top","right","bottom","left"],"uniqueKey":"steps.items.[INDEX].placement.oneOf.0.side","typeLabel":"enum\u003cstring>"},"align":{"description":"Alignment along the side. See `side`.","type":"string","enum":["start","center","end"],"uniqueKey":"steps.items.[INDEX].placement.oneOf.0.align","typeLabel":"enum\u003cstring>"},"sideOffset":{"description":"Pixels between the target and the tooltip, along `side`.","type":"number","uniqueKey":"steps.items.[INDEX].placement.oneOf.0.sideOffset","typeLabel":"number"},"alignOffset":{"description":"Pixel shift along the alignment axis. Only applies when `align` is `start` or `end` — at `center` alignment the runtime ignores it silently.","type":"number","uniqueKey":"steps.items.[INDEX].placement.oneOf.0.alignOffset","typeLabel":"number"},"alignType":{"description":"Position mode. `auto` auto-positions and flips to avoid the viewport edge (ignoring side/align); `fixed` pins to side/align without flipping. Usually omit it: providing side/align implies `fixed`, and omitting them implies `auto`. Set it only to override that (e.g. `auto` while still passing a side/align you want ignored). Read-backs always carry concrete side/align even in `auto` mode (the auto starting position, bottom/center) — `alignType` is what governs, and `auto` still flips at runtime.","type":"string","enum":["auto","fixed"],"uniqueKey":"steps.items.[INDEX].placement.oneOf.0.alignType","typeLabel":"enum\u003cstring>"},"backdrop":{"description":"Dim the rest of the page while this tooltip is up.","type":"boolean","uniqueKey":"steps.items.[INDEX].placement.oneOf.0.backdrop","typeLabel":"boolean"},"blockTarget":{"description":"Make the backdrop swallow clicks on the highlighted element, so the user must use the tooltip. REQUIRES `backdrop: true` — on its own it does nothing (the renderer only consults it inside the backdrop branch).","type":"boolean","uniqueKey":"steps.items.[INDEX].placement.oneOf.0.blockTarget","typeLabel":"boolean"}},"additionalProperties":false,"uniqueKey":"steps.items.[INDEX].placement.oneOf.0","typeLabel":"object"},{"type":"object","description":"Two placement shapes, by step kind: a TOOLTIP (anchored to a `target`) uses `{ side, align, sideOffset?, alignOffset?, alignType? }` positioned relative to the element; a MODAL uses `{ position, offsetX?, offsetY? }` on a 9-cell viewport grid (e.g. `\"center\"`). Both may set `backdrop`; `blockTarget` is TOOLTIP-ONLY (a modal already covers the page) and additionally requires `backdrop: true` — it makes the backdrop swallow clicks on the highlighted element. A BUBBLE step is positioned by its THEME's bubble placement, so the only key it accepts here is `{ backdrop }` (positional keys are rejected — move the bubble by changing the theme); a HIDDEN step renders no UI and rejects `placement` entirely.","required":["position"],"properties":{"position":{"type":"string","enum":["leftTop","centerTop","rightTop","leftCenter","center","rightCenter","leftBottom","centerBottom","rightBottom"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].placement.oneOf.1.position","typeLabel":"enum\u003cstring>"},"offsetX":{"description":"Pixel shift from the grid cell. Applies in every cell EXCEPT `position: \"center\"`, where both offsets are ignored silently.","type":"number","uniqueKey":"steps.items.[INDEX].placement.oneOf.1.offsetX","typeLabel":"number"},"offsetY":{"description":"See `offsetX`.","type":"number","uniqueKey":"steps.items.[INDEX].placement.oneOf.1.offsetY","typeLabel":"number"},"backdrop":{"type":"boolean","uniqueKey":"steps.items.[INDEX].placement.oneOf.1.backdrop","typeLabel":"boolean"}},"additionalProperties":false,"uniqueKey":"steps.items.[INDEX].placement.oneOf.1","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].placement","typeLabel":"object"},"width":{"description":"Step width in pixels — OVERRIDES the theme's surface width (tooltip.width / modal.width / bubble.width) for THIS step only; omit to use the theme width. Widths are border-box: the outer edge renders at exactly this value (border inside); the inner iframe measures 2x the theme border width less.","type":"number","uniqueKey":"steps.items.[INDEX].width","typeLabel":"number"},"skippable":{"type":"boolean","uniqueKey":"steps.items.[INDEX].skippable","typeLabel":"boolean"},"explicitCompletionStep":{"description":"Marks this step as an explicit completion point — reaching it completes the flow.","type":"boolean","uniqueKey":"steps.items.[INDEX].explicitCompletionStep","typeLabel":"boolean"},"content":{"description":"The step's body blocks. On an ECHOED step (matched by cvid/id), omit to keep the stored content unchanged — an explicit [] clears it. Same omit-keeps semantics as `triggers`.","type":"array","items":{"oneOf":[{"type":"object","required":["type","markdown"],"properties":{"type":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"markdown":{"type":"string","description":"A small markdown subset: paragraphs, `# `/`## ` headings (h1/h2 only — no h3+), `-`/`*` and `1.` lists, ``` code fences; inline `**bold**`, `*italic*`, `[text](url)`, and `{{ attribute_code | default: \"x\" }}` for user attributes. Anything outside this subset is SILENTLY normalized, not rejected: h3+ → h2; blockquotes flatten to paragraphs; tables, horizontal rules, strikethrough, inline images/code, and liquid filters other than `default` are dropped. Unsupported syntax won't round-trip — don't rely on it.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.0.markdown","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.0.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.0.id","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["image"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.id","typeLabel":"string"},"alt":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.alt","typeLabel":"string"},"link":{"type":"object","properties":{"url":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.link.url","typeLabel":"string"},"newTab":{"type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.link.newTab","typeLabel":"boolean"}},"required":["url"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.link","typeLabel":"object"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.width","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.margin.right","typeLabel":"number"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.margin","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","text"],"properties":{"type":{"type":"string","enum":["button"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"text":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.text","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.id","typeLabel":"string"},"actions":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions","typeLabel":"object[]"},"disabledWhen":{"description":"REACTIVE slot — polled live in the browser (the button disables the moment the conditions match). Client-evaluable condition types only: attribute / current_url / element / text_input / text_filled / time_window; event / segment / content_state are rejected (E1017).","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen","typeLabel":"object[]"},"hiddenWhen":{"description":"REACTIVE slot — polled live in the browser (the button shows/hides as conditions change). Same client-evaluable-only rule as `disabledWhen`.","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen","typeLabel":"object[]"},"variant":{"type":"string","enum":["primary","secondary"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.variant","typeLabel":"enum\u003cstring>"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.margin.right","typeLabel":"number"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.margin","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["embed"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"description":"Paste the page's normal URL (e.g. a youtube.com/watch link) — on write it is resolved through the standard oEmbed provider registry (YouTube, Vimeo, Loom, Figma, …) and the provider's official embed markup is stored, so you never hand-build /embed/ URLs. A URL no provider claims is iframed as-is — it renders only if that site allows being framed (no X-Frame-Options/CSP block).","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.id","typeLabel":"string"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.width","typeLabel":"object"},"height":{"description":"Embed height. Optional ONLY for provider embeds (YouTube/Vimeo/… size themselves by aspect ratio); a URL with NO oEmbed provider has no ratio, and omitting a pixel height leaves the iframe at the browser's built-in default — a strip ~150px tall, almost never the intended size (validate warns). For plain-iframe URLs always set `{ \"unit\": \"pixels\", \"value\": … }`.","type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.height.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.height.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.height","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.margin.right","typeLabel":"number"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.margin","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","question"],"properties":{"type":{"type":"string","enum":["question"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"question":{"oneOf":[{"type":"object","required":["kind","name"],"properties":{"kind":{"type":"string","enum":["nps"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.0.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.0.name","typeLabel":"string"},"cvid":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.0.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.0.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.0.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.0.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.0","typeLabel":"object"},{"type":"object","required":["kind","name","style","range"],"properties":{"kind":{"type":"string","enum":["rating"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.name","typeLabel":"string"},"style":{"type":"string","enum":["star","scale"],"description":"star = star rating; scale = a numeric scale. A \"scale\" question IS a rating with style:\"scale\" — there is no separate \"scale\" kind.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.style","typeLabel":"enum\u003cstring>"},"range":{"type":"object","properties":{"low":{"type":"number","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.range.low","typeLabel":"number"},"high":{"type":"number","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.range.high","typeLabel":"number"}},"required":["low","high"],"description":"Numeric range, e.g. { low: 1, high: 5 }.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.range","typeLabel":"object"},"cvid":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1","typeLabel":"object"},{"type":"object","required":["kind","name","multiline"],"properties":{"kind":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2.name","typeLabel":"string"},"multiline":{"type":"boolean","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2.multiline","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2.cvid","typeLabel":"string"},"placeholder":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2.placeholder","typeLabel":"string"},"buttonText":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2.buttonText","typeLabel":"string"},"required":{"description":"Require an answer before submit. ONLY `text` supports this — nps / rating / choice cannot be marked required.","type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2.required","typeLabel":"boolean"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2","typeLabel":"object"},{"type":"object","required":["kind","name","options","allowMultiple"],"properties":{"kind":{"type":"string","enum":["choice"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.name","typeLabel":"string"},"options":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].label","typeLabel":"string"},"value":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].value","typeLabel":"string"}},"required":["label","value"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX]","typeLabel":"object"},"description":"Each option has a human-facing `label` and a stored `value` — the `value` is what gets recorded/bound as the answer.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.options","typeLabel":"object[]"},"allowMultiple":{"type":"boolean","description":"false = single-select, true = multi-select. A multi-select answer needs a `list`-typed bound attribute.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.allowMultiple","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.cvid","typeLabel":"string"},"enableOther":{"type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.enableOther","typeLabel":"boolean"},"otherPlaceholder":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.otherPlaceholder","typeLabel":"string"},"shuffle":{"type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.shuffle","typeLabel":"boolean"},"buttonText":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.buttonText","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3","typeLabel":"object"}],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question","typeLabel":"object"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.id","typeLabel":"string"},"actions":{"description":"Actions that fire when this question is answered (on pick for nps/rating/single-select; on its Submit button for text/multi-select). Put a `goto_step` HERE to advance to the next step — without it the question records the answer but the flow does NOT advance (validate flags the next step \"not reachable\"). Do NOT add a separate `button` block just to advance: it doubles up with the question's own submit affordance.","type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions","typeLabel":"object[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Lays 2+ blocks out side by side in one row. Top-level `content` blocks otherwise stack vertically — each is its own full-width row — so two `button` blocks placed directly in `content` render as two stacked rows, NOT a button pair. For a modal/tooltip footer with a Skip + primary button: one `columns` block with two `{ width: { unit: \"fill\" }, blocks: [...] }` entries, one button per column.","required":["type","columns"],"properties":{"type":{"type":"string","enum":["columns"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"columns":{"type":"array","items":{"type":"object","properties":{"blocks":{"type":"array","items":{"$circularRef":"b55c69d5-0b98-4319-a584-ae0961850a0b"},"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].blocks","typeLabel":"array"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels","fill"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].width","typeLabel":"object"},"justify":{"description":"Horizontal alignment of this column's own block(s) within the column's width — use \"end\"/\"center\" to right-align or center a lone button (a single-column `columns` block).","type":"string","enum":["start","center","end","between","around","evenly"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].justify","typeLabel":"enum\u003cstring>"},"align":{"type":"string","enum":["start","center","end","baseline"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].align","typeLabel":"enum\u003cstring>"},"padding":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.right","typeLabel":"number"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding","typeLabel":"object"}},"required":["blocks"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX]","typeLabel":"object"},"description":"One entry per column, laid out left-to-right. Each column is a mini vertical stack of `blocks` (usually one).","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns","typeLabel":"object[]"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.id","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.6.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.6.id","typeLabel":"string"},"note":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.6.note","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.6","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].content","typeLabel":"object[]"},"triggers":{"type":"array","items":{"type":"object","properties":{"do":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX]","typeLabel":"object"},"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do","typeLabel":"object[]"},"when":{"description":"REACTIVE slot — polled live in the browser while the step is on screen, so it accepts only client-evaluable condition types: attribute / current_url / element / text_input / text_filled / time_window. Event / segment / content_state conditions are server-evaluated and rejected here (E1017). A trigger with NO `when` never fires (an empty set is \"not matched\", not \"always\").","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when","typeLabel":"object[]"},"waitSeconds":{"description":"Delay in SECONDS between the `when` conditions matching and the `do` actions firing. The timer arms the first time `when` matches and the actions fire after the wait EVEN IF the conditions have since stopped matching (the match is latched, not re-checked). Capped at 300 seconds by the runtime (a larger value is clamped).","type":"number","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].waitSeconds","typeLabel":"number"}},"required":["do"],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].triggers","typeLabel":"object[]"},"onClick":{"description":"Actions to run when the user clicks the step's TARGET ELEMENT on the page (click-to-advance) — distinct from a `button` block, whose actions fire on a button rendered inside the step. `tooltip` steps only (the other kinds have no target element); sending it on a modal / bubble / hidden step is rejected.","type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].onClick","typeLabel":"object[]"}},"required":["name","type"],"uniqueKey":"steps.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps","typeLabel":"object[]"},"startRules":{"oneOf":[{"type":"object","properties":{"when":{"description":"Start conditions (full replacement when present). Omit to keep the existing conditions and change only the settings.","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"startRules.oneOf.0.when.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"startRules.oneOf.0.when.items.[INDEX]","typeLabel":"object"},"uniqueKey":"startRules.oneOf.0.when","typeLabel":"object[]"},"frequency":{"description":"How often the content may auto-start (flow/checklist only). Left unset on a write, the builder default (`once`) is seeded and STORED — read-backs show the effective mode explicitly. A version stored with NO frequency at all (reachable only through legacy/builder-external writes, never through this API) runs with NO limit: it starts again every time its rules match once the prior session ends — validate_content_version warns on that state.","type":"object","properties":{"mode":{"type":"string","enum":["once","multiple","unlimited"],"description":"once = show a single time; multiple = up to N times per window; unlimited = every time the conditions match (subject to `every`).","isRequired":true,"uniqueKey":"startRules.oneOf.0.frequency.mode","typeLabel":"enum\u003cstring>"},"every":{"description":"Re-show window. Used by `multiple` (with `times`) and `unlimited`; ignored for `once`. If omitted for those modes a default window is applied. Manual and programmatic starts also count toward the `multiple` limit.","type":"object","properties":{"duration":{"type":"number","isRequired":true,"uniqueKey":"startRules.oneOf.0.frequency.every.duration","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.frequency.every.unit","typeLabel":"enum\u003cstring>"},"times":{"type":"number","uniqueKey":"startRules.oneOf.0.frequency.every.times","typeLabel":"number"}},"required":["duration","unit"],"uniqueKey":"startRules.oneOf.0.frequency.every","typeLabel":"object"},"atLeast":{"description":"Only auto-start if no OTHER content of the SAME type has been shown within this window — and since only flows accept this knob, in practice: no other FLOW. A banner / checklist / launcher showing does NOT block it. Avoids stacking several flows on a user at once.","type":"object","properties":{"duration":{"type":"number","isRequired":true,"uniqueKey":"startRules.oneOf.0.frequency.atLeast.duration","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"isRequired":true,"uniqueKey":"startRules.oneOf.0.frequency.atLeast.unit","typeLabel":"enum\u003cstring>"}},"required":["duration","unit"],"uniqueKey":"startRules.oneOf.0.frequency.atLeast","typeLabel":"object"}},"required":["mode"],"uniqueKey":"startRules.oneOf.0.frequency","typeLabel":"object"},"priority":{"description":"Tie-breaker when a user matches the start conditions for more than one piece of content at the same time — the higher priority starts first. Content with no priority set ranks as `medium`.","type":"string","enum":["highest","high","medium","low","lowest"],"uniqueKey":"startRules.oneOf.0.priority","typeLabel":"enum\u003cstring>"},"waitSeconds":{"description":"Delay in SECONDS between the start conditions matching and the content becoming ELIGIBLE to start. The countdown itself survives the conditions un-matching mid-wait, but unlike a trigger wait (which fires its actions regardless), an elapsed start-rule wait guarantees nothing: the conditions are RE-CHECKED at show time, and the content starts at the next moment they match again. One exception: when a session of the same content type starts or ends mid-wait, in-flight timers are cancelled and re-armed — a still-matching version then restarts its wait from zero. Capped at 300 seconds by the runtime (a larger value is clamped).","type":"number","uniqueKey":"startRules.oneOf.0.waitSeconds","typeLabel":"number"},"startIfNotComplete":{"description":"When true, this content won't auto-start for users who have already completed it.","type":"boolean","uniqueKey":"startRules.oneOf.0.startIfNotComplete","typeLabel":"boolean"}},"description":"PATCHES the stored rules field-by-field: an omitted setting (frequency / priority / waitSeconds / startIfNotComplete) keeps its stored value — including one inherited from the forked version — so to turn a setting off, send it explicitly (e.g. `startIfNotComplete: false`). `when`, when present, fully replaces the condition list. `null` clears the rules entirely (content stops auto-starting). Which settings and which `when` condition types the CONTENT TYPE supports varies (e.g. no priority on a launcher, attribute/segment-only targeting on an announcement) — unsupported ones are rejected per type.","uniqueKey":"startRules.oneOf.0","typeLabel":"object"},{"type":"null","properties":{"when":{"description":"Start conditions (full replacement when present). Omit to keep the existing conditions and change only the settings.","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"]},"match":{"type":"string","enum":["all","any"]},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped."}}},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"]},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope."},"attribute":{"type":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`)."},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"]},"segment":{"type":"string"},"in":{"type":"boolean"}}},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"]},"includes":{"type":"array","items":{"type":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed)."},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"]},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render."},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it."}}},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"]},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write."},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A."}}},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"]},"event":{"type":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"]},"n":{"type":"integer","minimum":0,"maximum":9007199254740991},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["op","n"]},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"]},"value":{"type":"number"},"value2":{"type":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"]}},"required":["op"]},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"]},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"]},"attribute":{"type":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"]},"value":{"type":"string"},"value2":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"]},"match":{"type":"string","enum":["all","any"]},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped."}}}]}}}},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"]},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render."},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"]},"value":{"type":"string"}}},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"]},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render."},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]}}},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"]},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X."},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string"}}},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"]},"note":{"type":"string"}}}]}},"frequency":{"description":"How often the content may auto-start (flow/checklist only). Left unset on a write, the builder default (`once`) is seeded and STORED — read-backs show the effective mode explicitly. A version stored with NO frequency at all (reachable only through legacy/builder-external writes, never through this API) runs with NO limit: it starts again every time its rules match once the prior session ends — validate_content_version warns on that state.","type":"object","properties":{"mode":{"type":"string","enum":["once","multiple","unlimited"],"description":"once = show a single time; multiple = up to N times per window; unlimited = every time the conditions match (subject to `every`)."},"every":{"description":"Re-show window. Used by `multiple` (with `times`) and `unlimited`; ignored for `once`. If omitted for those modes a default window is applied. Manual and programmatic starts also count toward the `multiple` limit.","type":"object","properties":{"times":{"type":"number"},"duration":{"type":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"]}},"required":["duration","unit"]},"atLeast":{"description":"Only auto-start if no OTHER content of the SAME type has been shown within this window — and since only flows accept this knob, in practice: no other FLOW. A banner / checklist / launcher showing does NOT block it. Avoids stacking several flows on a user at once.","type":"object","properties":{"duration":{"type":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"]}},"required":["duration","unit"]}},"required":["mode"]},"priority":{"description":"Tie-breaker when a user matches the start conditions for more than one piece of content at the same time — the higher priority starts first. Content with no priority set ranks as `medium`.","type":"string","enum":["highest","high","medium","low","lowest"]},"waitSeconds":{"description":"Delay in SECONDS between the start conditions matching and the content becoming ELIGIBLE to start. The countdown itself survives the conditions un-matching mid-wait, but unlike a trigger wait (which fires its actions regardless), an elapsed start-rule wait guarantees nothing: the conditions are RE-CHECKED at show time, and the content starts at the next moment they match again. One exception: when a session of the same content type starts or ends mid-wait, in-flight timers are cancelled and re-armed — a still-matching version then restarts its wait from zero. Capped at 300 seconds by the runtime (a larger value is clamped).","type":"number"},"startIfNotComplete":{"description":"When true, this content won't auto-start for users who have already completed it.","type":"boolean"}},"description":"PATCHES the stored rules field-by-field: an omitted setting (frequency / priority / waitSeconds / startIfNotComplete) keeps its stored value — including one inherited from the forked version — so to turn a setting off, send it explicitly (e.g. `startIfNotComplete: false`). `when`, when present, fully replaces the condition list. `null` clears the rules entirely (content stops auto-starting). Which settings and which `when` condition types the CONTENT TYPE supports varies (e.g. no priority on a launcher, attribute/segment-only targeting on an announcement) — unsupported ones are rejected per type.","uniqueKey":"startRules.oneOf.1","typeLabel":"null"}],"uniqueKey":"startRules","typeLabel":"object | null"},"hideRules":{"oneOf":[{"type":"object","properties":{"when":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"hideRules.oneOf.0.when.items.[INDEX]","typeLabel":"object"},"isRequired":true,"uniqueKey":"hideRules.oneOf.0.when","typeLabel":"object[]"}},"required":["when"],"description":"Temporarily-hide rules. Only some content types support them (rejected per type).","uniqueKey":"hideRules.oneOf.0","typeLabel":"object"},{"type":"null","properties":{"when":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"]},"match":{"type":"string","enum":["all","any"]},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped."}}},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"]},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope."},"attribute":{"type":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`)."},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"]},"segment":{"type":"string"},"in":{"type":"boolean"}}},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"]},"includes":{"type":"array","items":{"type":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed)."},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"]},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render."},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it."}}},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"]},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write."},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A."}}},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"]},"event":{"type":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"]},"n":{"type":"integer","minimum":0,"maximum":9007199254740991},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["op","n"]},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"]},"value":{"type":"number"},"value2":{"type":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"]}},"required":["op"]},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"]},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"]},"attribute":{"type":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"]},"value":{"type":"string"},"value2":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"]},"match":{"type":"string","enum":["all","any"]},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped."}}}]}}}},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"]},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render."},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"]},"value":{"type":"string"}}},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"]},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render."},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]}}},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"]},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X."},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string"}}},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"]},"note":{"type":"string"}}}]}}},"required":["when"],"description":"Temporarily-hide rules. Only some content types support them (rejected per type).","uniqueKey":"hideRules.oneOf.1","typeLabel":"null"}],"uniqueKey":"hideRules","typeLabel":"object | null"},"themeId":{"description":"Theme to apply (cannot be cleared).","type":"string","uniqueKey":"themeId","typeLabel":"string"},"data":{"oneOf":[{"type":"object","description":"Type-specific body for a non-flow content version: checklist / launcher / banner / tracker / announcement / resource-center. Field-level merged onto the existing data.","properties":{"buttonText":{"description":"Label on the collapsed checklist launcher pill (e.g. \"Getting started\"). Plain string — NO `{{ }}` interpolation (braces would render literally).","type":"string","uniqueKey":"data.oneOf.0.buttonText","typeLabel":"string"},"initialDisplay":{"description":"How the checklist first appears: `expanded` shows the whole checklist (tasks and all); `button` shows just the launcher button.","type":"string","enum":["expanded","button"],"uniqueKey":"data.oneOf.0.initialDisplay","typeLabel":"enum\u003cstring>"},"completionOrder":{"description":"Whether tasks can be completed in `any` order, or must be completed `ordered` (top to bottom among the tasks the user can currently SEE — a task hidden by `onlyShowWhen` does not block the ones after it). This is the ONLY built-in cross-task sequencing — there is no per-task \"after task X\" condition; for finer dependencies gate a task's `onlyShowWhen` on the shared event/state that completes its prerequisite.","type":"string","enum":["any","ordered"],"uniqueKey":"data.oneOf.0.completionOrder","typeLabel":"enum\u003cstring>"},"preventDismiss":{"description":"When true, users can't dismiss the checklist.","type":"boolean","uniqueKey":"data.oneOf.0.preventDismiss","typeLabel":"boolean"},"autoDismiss":{"description":"When true, the checklist closes on its own once every task is done.","type":"boolean","uniqueKey":"data.oneOf.0.autoDismiss","typeLabel":"boolean"},"content":{"description":"Rich content shown at the top of the expanded panel, above the task list — typically a short welcome line framing what the tasks achieve.","type":"array","items":{"oneOf":[{"type":"object","required":["type","markdown"],"properties":{"type":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"markdown":{"type":"string","description":"A small markdown subset: paragraphs, `# `/`## ` headings (h1/h2 only — no h3+), `-`/`*` and `1.` lists, ``` code fences; inline `**bold**`, `*italic*`, `[text](url)`, and `{{ attribute_code | default: \"x\" }}` for user attributes. Anything outside this subset is SILENTLY normalized, not rejected: h3+ → h2; blockquotes flatten to paragraphs; tables, horizontal rules, strikethrough, inline images/code, and liquid filters other than `default` are dropped. Unsupported syntax won't round-trip — don't rely on it.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.0.markdown","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.0.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.0.id","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["image"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.id","typeLabel":"string"},"alt":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.alt","typeLabel":"string"},"link":{"type":"object","properties":{"url":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.link.url","typeLabel":"string"},"newTab":{"type":"boolean","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.link.newTab","typeLabel":"boolean"}},"required":["url"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.link","typeLabel":"object"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.width","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","text"],"properties":{"type":{"type":"string","enum":["button"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"text":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.text","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.id","typeLabel":"string"},"actions":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.actions","typeLabel":"object[]"},"disabledWhen":{"description":"REACTIVE slot — polled live in the browser (the button disables the moment the conditions match). Client-evaluable condition types only: attribute / current_url / element / text_input / text_filled / time_window; event / segment / content_state are rejected (E1017).","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen","typeLabel":"object[]"},"hiddenWhen":{"description":"REACTIVE slot — polled live in the browser (the button shows/hides as conditions change). Same client-evaluable-only rule as `disabledWhen`.","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen","typeLabel":"object[]"},"variant":{"type":"string","enum":["primary","secondary"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.variant","typeLabel":"enum\u003cstring>"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["embed"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"description":"Paste the page's normal URL (e.g. a youtube.com/watch link) — on write it is resolved through the standard oEmbed provider registry (YouTube, Vimeo, Loom, Figma, …) and the provider's official embed markup is stored, so you never hand-build /embed/ URLs. A URL no provider claims is iframed as-is — it renders only if that site allows being framed (no X-Frame-Options/CSP block).","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3.id","typeLabel":"string"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3.width","typeLabel":"object"},"height":{"description":"Embed height. Optional ONLY for provider embeds (YouTube/Vimeo/… size themselves by aspect ratio); a URL with NO oEmbed provider has no ratio, and omitting a pixel height leaves the iframe at the browser's built-in default — a strip ~150px tall, almost never the intended size (validate warns). For plain-iframe URLs always set `{ \"unit\": \"pixels\", \"value\": … }`.","type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3.height.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3.height.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3.height","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","question"],"properties":{"type":{"type":"string","enum":["question"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"question":{"oneOf":[{"type":"object","required":["kind","name"],"properties":{"kind":{"type":"string","enum":["nps"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.0.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.0.name","typeLabel":"string"},"cvid":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.0.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.0.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.0.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.0.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.0","typeLabel":"object"},{"type":"object","required":["kind","name","style","range"],"properties":{"kind":{"type":"string","enum":["rating"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.name","typeLabel":"string"},"style":{"type":"string","enum":["star","scale"],"description":"star = star rating; scale = a numeric scale. A \"scale\" question IS a rating with style:\"scale\" — there is no separate \"scale\" kind.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.style","typeLabel":"enum\u003cstring>"},"range":{"type":"object","properties":{"low":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.range.low","typeLabel":"number"},"high":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.range.high","typeLabel":"number"}},"required":["low","high"],"description":"Numeric range, e.g. { low: 1, high: 5 }.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.range","typeLabel":"object"},"cvid":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1","typeLabel":"object"},{"type":"object","required":["kind","name","multiline"],"properties":{"kind":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2.name","typeLabel":"string"},"multiline":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2.multiline","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2.cvid","typeLabel":"string"},"placeholder":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2.placeholder","typeLabel":"string"},"buttonText":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2.buttonText","typeLabel":"string"},"required":{"description":"Require an answer before submit. ONLY `text` supports this — nps / rating / choice cannot be marked required.","type":"boolean","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2.required","typeLabel":"boolean"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2","typeLabel":"object"},{"type":"object","required":["kind","name","options","allowMultiple"],"properties":{"kind":{"type":"string","enum":["choice"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.name","typeLabel":"string"},"options":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].label","typeLabel":"string"},"value":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].value","typeLabel":"string"}},"required":["label","value"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX]","typeLabel":"object"},"description":"Each option has a human-facing `label` and a stored `value` — the `value` is what gets recorded/bound as the answer.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.options","typeLabel":"object[]"},"allowMultiple":{"type":"boolean","description":"false = single-select, true = multi-select. A multi-select answer needs a `list`-typed bound attribute.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.allowMultiple","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.cvid","typeLabel":"string"},"enableOther":{"type":"boolean","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.enableOther","typeLabel":"boolean"},"otherPlaceholder":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.otherPlaceholder","typeLabel":"string"},"shuffle":{"type":"boolean","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.shuffle","typeLabel":"boolean"},"buttonText":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.buttonText","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3","typeLabel":"object"}],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.question","typeLabel":"object"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.id","typeLabel":"string"},"actions":{"description":"Actions that fire when this question is answered (on pick for nps/rating/single-select; on its Submit button for text/multi-select). Put a `goto_step` HERE to advance to the next step — without it the question records the answer but the flow does NOT advance (validate flags the next step \"not reachable\"). Do NOT add a separate `button` block just to advance: it doubles up with the question's own submit affordance.","type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4.actions","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Lays 2+ blocks out side by side in one row. Top-level `content` blocks otherwise stack vertically — each is its own full-width row — so two `button` blocks placed directly in `content` render as two stacked rows, NOT a button pair. For a modal/tooltip footer with a Skip + primary button: one `columns` block with two `{ width: { unit: \"fill\" }, blocks: [...] }` entries, one button per column.","required":["type","columns"],"properties":{"type":{"type":"string","enum":["columns"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"columns":{"type":"array","items":{"type":"object","properties":{"blocks":{"type":"array","items":{"$circularRef":"b55c69d5-0b98-4319-a584-ae0961850a0b"},"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].blocks","typeLabel":"array"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels","fill"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].width","typeLabel":"object"},"justify":{"description":"Horizontal alignment of this column's own block(s) within the column's width — use \"end\"/\"center\" to right-align or center a lone button (a single-column `columns` block).","type":"string","enum":["start","center","end","between","around","evenly"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].justify","typeLabel":"enum\u003cstring>"},"align":{"type":"string","enum":["start","center","end","baseline"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].align","typeLabel":"enum\u003cstring>"},"padding":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding","typeLabel":"object"}},"required":["blocks"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX]","typeLabel":"object"},"description":"One entry per column, laid out left-to-right. Each column is a mini vertical stack of `blocks` (usually one).","isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.columns","typeLabel":"object[]"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5.id","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.6.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.6.id","typeLabel":"string"},"note":{"type":"string","uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.6.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.content.items.[INDEX].oneOf.6","typeLabel":"object"}],"uniqueKey":"data.oneOf.0.content.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.0.content","typeLabel":"object[]"},"items":{"description":"The checklist tasks. To be usable (enforced at publish) each item needs a `name` AND at least one of `completeWhen` (how it auto-completes) or `clickActions` (what its row does) — an item with neither is a dead row. `task_clicked` in `completeWhen` is the only completion that needs no app instrumentation.","type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"The visible task row label. Plain string — NO `{{ }}` interpolation (braces would render literally).","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].name","typeLabel":"string"},"id":{"description":"Server-owned task identity — ECHO it back when rewriting `items`. An item written without its existing id gets a NEW one: in-flight users' completion state for it resets and its per-task analytics rows break. Omit only for a genuinely new task.","type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].id","typeLabel":"string"},"description":{"description":"Optional supporting text rendered below the task name. A short benefit statement or time estimate (\"Send an invite so your team can collaborate\", \"~30 sec\") measurably lifts task click-through — prefer setting it over leaving the row name-only. Plain string — NO `{{ }}` interpolation (braces would render literally).","type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].description","typeLabel":"string"},"completeWhen":{"default":[],"description":"Condition(s) that mark THIS task done. Use [{ \"type\": \"task_clicked\" }] to complete it when the user clicks the task — the only option that needs no app instrumentation; other conditions (event / element / segment / current_url / attribute) require the matching wiring or data in your app. Empty = the task never auto-completes.","type":"array","items":{"oneOf":[{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["task_clicked"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"9f4acbd4-53af-453b-8d42-16f10d97fbfb"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.2.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.2.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.3.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.3.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.3.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.3.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.3.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.3.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.3.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.4.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.4.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.5.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.5.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.5.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.5.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.6.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.6.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.6.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.6.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.6.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.7.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.7.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.9.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.9.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.9.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.9.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.9.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.9.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.10.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.10.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.10.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.10.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.10","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.11.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.11.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.11.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.11","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.12.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.12.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX].oneOf.12","typeLabel":"object"}],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].completeWhen","typeLabel":"object[]"},"clickActions":{"default":[],"description":"What happens when the user CLICKS the task row (e.g. [{ \"type\": \"navigate\", \"url\": \"/x\" }]) — a side effect, NOT completion. To also mark the task done on that click, add { \"type\": \"task_clicked\" } to completeWhen.","type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].clickActions","typeLabel":"object[]"},"onlyShowWhen":{"description":"Condition(s) that gate whether this task is VISIBLE (distinct from `completeWhen`, which marks it done). Omit = always shown. There is no \"task X is completed\" condition, so to make this task appear only after another is done, gate on the same event/state that completes that other task — you cannot reference another task directly.","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.0.\\items.items.[INDEX].onlyShowWhen","typeLabel":"object[]"}},"required":["name"],"additionalProperties":false,"uniqueKey":"data.oneOf.0.\\items.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.0.\\items","typeLabel":"object[]"}},"additionalProperties":{"oneOf":[{}],"uniqueKey":"data.oneOf.0.additionalProperties","typeLabel":"any"},"uniqueKey":"data.oneOf.0","typeLabel":"object"},{"type":"object","description":"Type-specific body for a non-flow content version: checklist / launcher / banner / tracker / announcement / resource-center. Field-level merged onto the existing data.","properties":{"style":{"description":"Visual form: `beacon` = pulsing dot, `icon` = a static icon (see `icon`), `button` = a text button (see `buttonText`), `hidden` = no visual — interactions on the target element itself drive `behavior`.","type":"string","enum":["beacon","icon","hidden","button"],"uniqueKey":"data.oneOf.1.style","typeLabel":"enum\u003cstring>"},"icon":{"type":"object","properties":{"source":{"description":"Where the icon comes from: `builtin` uses `type` (a RemixIcon name), `upload`/`url` use `url`. A launcher always renders an icon — there is no `none` / `inherit` here (the resource-center block icons are a different field and DO support them); to keep a launcher off the page, gate it with start rules instead.","type":"string","enum":["builtin","upload","url"],"uniqueKey":"data.oneOf.1.icon.source","typeLabel":"enum\u003cstring>"},"url":{"description":"Image URL for the icon — only used when source is 'upload' or 'url'.","type":"string","uniqueKey":"data.oneOf.1.icon.url","typeLabel":"string"},"type":{"description":"Builtin icon name (when source='builtin'): a RemixIcon name in kebab `-line`/`-fill` style — e.g. `home-line`, `question-line`, `rocket`. NOT lucide names (`help-circle` / `sparkles` / `book-open`): a name outside the registry is rejected by validate/publish with the bad name spelled out (it would render nothing). Common names + an intent→name table are in the MCP authoring guide (icons section).","type":"string","uniqueKey":"data.oneOf.1.icon.type","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.icon","typeLabel":"object"},"buttonText":{"description":"Label of the button — only rendered when style is 'button'. Plain string — NO `{{ }}` interpolation.","type":"string","uniqueKey":"data.oneOf.1.buttonText","typeLabel":"string"},"target":{"description":"The page element the launcher anchors to (selector + beacon placement on it).","type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.1.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.1.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.1.target.nth","typeLabel":"integer"},"placement":{"description":"Where the beacon sits relative to its target element. Omit to center it on the target. (The tooltip has its own `tooltip.placement`; this positions the launcher itself.) Read back ONLY when a side/align is pinned — an auto-centered beacon omits it.","type":"object","properties":{"side":{"description":"Which side of the target the beacon sits on. OMIT side+align to center the beacon on the target (with viewport-edge flipping); setting side (or align) pins it there.","type":"string","enum":["top","right","bottom","left"],"uniqueKey":"data.oneOf.1.target.placement.side","typeLabel":"enum\u003cstring>"},"align":{"description":"Alignment along the side. See `side`.","type":"string","enum":["start","center","end"],"uniqueKey":"data.oneOf.1.target.placement.align","typeLabel":"enum\u003cstring>"},"sideOffset":{"description":"Gap in pixels between the beacon and the target edge, along `side`.","type":"number","uniqueKey":"data.oneOf.1.target.placement.sideOffset","typeLabel":"number"},"alignOffset":{"description":"Pixel shift along the alignment axis. Only applies when `align` is `start`/`end` — at `center` (or under `auto`) the runtime ignores it.","type":"number","uniqueKey":"data.oneOf.1.target.placement.alignOffset","typeLabel":"number"},"alignType":{"description":"Position mode. `auto` centers on the target and flips at the viewport edge (ignoring side/align); `fixed` pins to side/align. Providing side/align implies `fixed`, omitting them implies `auto` — so leave `auto` and the beacon centers regardless of `align`.","type":"string","enum":["auto","fixed"],"uniqueKey":"data.oneOf.1.target.placement.alignType","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.1.target.placement","typeLabel":"object"}},"required":["selector"],"uniqueKey":"data.oneOf.1.target","typeLabel":"object"},"zIndex":{"description":"Stacking order (CSS z-index). Integer; negative values are allowed. Omit to use the SDK's default stacking.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"uniqueKey":"data.oneOf.1.zIndex","typeLabel":"integer"},"tooltip":{"type":"object","properties":{"placement":{"description":"Where the tooltip opens relative to its anchor (see `reference`).","type":"object","properties":{"side":{"description":"Which side of the target the tooltip opens on. OMIT side+align to auto-position (picks a spot + flips to avoid the viewport edge); setting side (or align) pins that direction (no auto-flip). Same auto/fixed derivation as a flow tooltip.","type":"string","enum":["top","right","bottom","left"],"uniqueKey":"data.oneOf.1.tooltip.placement.side","typeLabel":"enum\u003cstring>"},"align":{"description":"Alignment along the side. See `side`.","type":"string","enum":["start","center","end"],"uniqueKey":"data.oneOf.1.tooltip.placement.align","typeLabel":"enum\u003cstring>"},"sideOffset":{"description":"Gap in pixels between the tooltip and its anchor, along `side`.","type":"number","uniqueKey":"data.oneOf.1.tooltip.placement.sideOffset","typeLabel":"number"},"alignOffset":{"description":"Pixel shift along the alignment axis. Only applies when `align` is `start`/`end`.","type":"number","uniqueKey":"data.oneOf.1.tooltip.placement.alignOffset","typeLabel":"number"},"alignType":{"description":"Position mode. `auto` auto-positions + flips (ignoring side/align); `fixed` pins to side/align. Usually omit — providing side/align implies `fixed`, omitting them implies `auto`. Without this, an `auto` launcher renders center and your `align` is ignored.","type":"string","enum":["auto","fixed"],"uniqueKey":"data.oneOf.1.tooltip.placement.alignType","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.1.tooltip.placement","typeLabel":"object"},"width":{"description":"Tooltip width in pixels. Omit to use the theme's tooltip.width.","type":"number","uniqueKey":"data.oneOf.1.tooltip.width","typeLabel":"number"},"reference":{"description":"Whether the tooltip anchors to the target element or to the launcher itself.","type":"string","enum":["target","launcher"],"uniqueKey":"data.oneOf.1.tooltip.reference","typeLabel":"enum\u003cstring>"},"content":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","markdown"],"properties":{"type":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"markdown":{"type":"string","description":"A small markdown subset: paragraphs, `# `/`## ` headings (h1/h2 only — no h3+), `-`/`*` and `1.` lists, ``` code fences; inline `**bold**`, `*italic*`, `[text](url)`, and `{{ attribute_code | default: \"x\" }}` for user attributes. Anything outside this subset is SILENTLY normalized, not rejected: h3+ → h2; blockquotes flatten to paragraphs; tables, horizontal rules, strikethrough, inline images/code, and liquid filters other than `default` are dropped. Unsupported syntax won't round-trip — don't rely on it.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.0.markdown","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.0.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.0.id","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["image"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.id","typeLabel":"string"},"alt":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.alt","typeLabel":"string"},"link":{"type":"object","properties":{"url":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.link.url","typeLabel":"string"},"newTab":{"type":"boolean","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.link.newTab","typeLabel":"boolean"}},"required":["url"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.link","typeLabel":"object"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.width","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","text"],"properties":{"type":{"type":"string","enum":["button"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"text":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.text","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.id","typeLabel":"string"},"actions":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.actions","typeLabel":"object[]"},"disabledWhen":{"description":"REACTIVE slot — polled live in the browser (the button disables the moment the conditions match). Client-evaluable condition types only: attribute / current_url / element / text_input / text_filled / time_window; event / segment / content_state are rejected (E1017).","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.disabledWhen","typeLabel":"object[]"},"hiddenWhen":{"description":"REACTIVE slot — polled live in the browser (the button shows/hides as conditions change). Same client-evaluable-only rule as `disabledWhen`.","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.hiddenWhen","typeLabel":"object[]"},"variant":{"type":"string","enum":["primary","secondary"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.variant","typeLabel":"enum\u003cstring>"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["embed"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"description":"Paste the page's normal URL (e.g. a youtube.com/watch link) — on write it is resolved through the standard oEmbed provider registry (YouTube, Vimeo, Loom, Figma, …) and the provider's official embed markup is stored, so you never hand-build /embed/ URLs. A URL no provider claims is iframed as-is — it renders only if that site allows being framed (no X-Frame-Options/CSP block).","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3.id","typeLabel":"string"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3.width","typeLabel":"object"},"height":{"description":"Embed height. Optional ONLY for provider embeds (YouTube/Vimeo/… size themselves by aspect ratio); a URL with NO oEmbed provider has no ratio, and omitting a pixel height leaves the iframe at the browser's built-in default — a strip ~150px tall, almost never the intended size (validate warns). For plain-iframe URLs always set `{ \"unit\": \"pixels\", \"value\": … }`.","type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3.height.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3.height.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3.height","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","question"],"properties":{"type":{"type":"string","enum":["question"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"question":{"oneOf":[{"type":"object","required":["kind","name"],"properties":{"kind":{"type":"string","enum":["nps"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.0.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.0.name","typeLabel":"string"},"cvid":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.0.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.0.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.0.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.0.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.0","typeLabel":"object"},{"type":"object","required":["kind","name","style","range"],"properties":{"kind":{"type":"string","enum":["rating"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.1.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.1.name","typeLabel":"string"},"style":{"type":"string","enum":["star","scale"],"description":"star = star rating; scale = a numeric scale. A \"scale\" question IS a rating with style:\"scale\" — there is no separate \"scale\" kind.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.1.style","typeLabel":"enum\u003cstring>"},"range":{"type":"object","properties":{"low":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.1.range.low","typeLabel":"number"},"high":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.1.range.high","typeLabel":"number"}},"required":["low","high"],"description":"Numeric range, e.g. { low: 1, high: 5 }.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.1.range","typeLabel":"object"},"cvid":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.1.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.1.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.1.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.1.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.1","typeLabel":"object"},{"type":"object","required":["kind","name","multiline"],"properties":{"kind":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.2.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.2.name","typeLabel":"string"},"multiline":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.2.multiline","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.2.cvid","typeLabel":"string"},"placeholder":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.2.placeholder","typeLabel":"string"},"buttonText":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.2.buttonText","typeLabel":"string"},"required":{"description":"Require an answer before submit. ONLY `text` supports this — nps / rating / choice cannot be marked required.","type":"boolean","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.2.required","typeLabel":"boolean"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.2.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.2","typeLabel":"object"},{"type":"object","required":["kind","name","options","allowMultiple"],"properties":{"kind":{"type":"string","enum":["choice"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.3.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.3.name","typeLabel":"string"},"options":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].label","typeLabel":"string"},"value":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].value","typeLabel":"string"}},"required":["label","value"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX]","typeLabel":"object"},"description":"Each option has a human-facing `label` and a stored `value` — the `value` is what gets recorded/bound as the answer.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.3.options","typeLabel":"object[]"},"allowMultiple":{"type":"boolean","description":"false = single-select, true = multi-select. A multi-select answer needs a `list`-typed bound attribute.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.3.allowMultiple","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.3.cvid","typeLabel":"string"},"enableOther":{"type":"boolean","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.3.enableOther","typeLabel":"boolean"},"otherPlaceholder":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.3.otherPlaceholder","typeLabel":"string"},"shuffle":{"type":"boolean","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.3.shuffle","typeLabel":"boolean"},"buttonText":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.3.buttonText","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.3.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question.oneOf.3","typeLabel":"object"}],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.question","typeLabel":"object"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.id","typeLabel":"string"},"actions":{"description":"Actions that fire when this question is answered (on pick for nps/rating/single-select; on its Submit button for text/multi-select). Put a `goto_step` HERE to advance to the next step — without it the question records the answer but the flow does NOT advance (validate flags the next step \"not reachable\"). Do NOT add a separate `button` block just to advance: it doubles up with the question's own submit affordance.","type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4.actions","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Lays 2+ blocks out side by side in one row. Top-level `content` blocks otherwise stack vertically — each is its own full-width row — so two `button` blocks placed directly in `content` render as two stacked rows, NOT a button pair. For a modal/tooltip footer with a Skip + primary button: one `columns` block with two `{ width: { unit: \"fill\" }, blocks: [...] }` entries, one button per column.","required":["type","columns"],"properties":{"type":{"type":"string","enum":["columns"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"columns":{"type":"array","items":{"type":"object","properties":{"blocks":{"type":"array","items":{"$circularRef":"b55c69d5-0b98-4319-a584-ae0961850a0b"},"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.columns.items.[INDEX].blocks","typeLabel":"array"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels","fill"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.columns.items.[INDEX].width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.columns.items.[INDEX].width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.columns.items.[INDEX].width","typeLabel":"object"},"justify":{"description":"Horizontal alignment of this column's own block(s) within the column's width — use \"end\"/\"center\" to right-align or center a lone button (a single-column `columns` block).","type":"string","enum":["start","center","end","between","around","evenly"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.columns.items.[INDEX].justify","typeLabel":"enum\u003cstring>"},"align":{"type":"string","enum":["start","center","end","baseline"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.columns.items.[INDEX].align","typeLabel":"enum\u003cstring>"},"padding":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding","typeLabel":"object"}},"required":["blocks"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.columns.items.[INDEX]","typeLabel":"object"},"description":"One entry per column, laid out left-to-right. Each column is a mini vertical stack of `blocks` (usually one).","isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.columns","typeLabel":"object[]"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5.id","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.6.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.6.id","typeLabel":"string"},"note":{"type":"string","uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.6.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX].oneOf.6","typeLabel":"object"}],"uniqueKey":"data.oneOf.1.tooltip.content.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.1.tooltip.content","typeLabel":"object[]"},"settings":{"type":"object","properties":{"dismissAfterFirstActivation":{"description":"Dismiss the launcher after its tooltip is first shown and closed.","type":"boolean","uniqueKey":"data.oneOf.1.tooltip.settings.dismissAfterFirstActivation","typeLabel":"boolean"},"keepOpenWhenHovered":{"description":"READ-ONLY — not wired at runtime: the tooltip ALWAYS stays open while hovered regardless of this value. Echoed for round-trip; changing it is rejected.","type":"boolean","uniqueKey":"data.oneOf.1.tooltip.settings.keepOpenWhenHovered","typeLabel":"boolean"},"hideLauncherWhenTooltipShown":{"description":"READ-ONLY — not wired at runtime: the launcher is NEVER hidden while its tooltip shows. Echoed for round-trip; changing it is rejected.","type":"boolean","uniqueKey":"data.oneOf.1.tooltip.settings.hideLauncherWhenTooltipShown","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.1.tooltip.settings","typeLabel":"object"}},"uniqueKey":"data.oneOf.1.tooltip","typeLabel":"object"},"behavior":{"description":"How users interact with the launcher and what that interaction does. Omit for the default (click the launcher to show the tooltip).","type":"object","properties":{"triggerElement":{"description":"Which element listens for the interaction: the launcher visual, the target element itself, or either. With style 'hidden' the target is the only thing to interact with.","type":"string","enum":["launcher","target","target-or-launcher"],"uniqueKey":"data.oneOf.1.behavior.triggerElement","typeLabel":"enum\u003cstring>"},"event":{"description":"The interaction that triggers the launcher: click or hover.","type":"string","enum":["clicked","hovered"],"uniqueKey":"data.oneOf.1.behavior.event","typeLabel":"enum\u003cstring>"},"action":{"description":"What the interaction does: 'show-tooltip' opens `tooltip.content`; 'perform-action' runs `actions` directly (e.g. start a flow) with no tooltip.","type":"string","enum":["show-tooltip","perform-action"],"uniqueKey":"data.oneOf.1.behavior.action","typeLabel":"enum\u003cstring>"},"actions":{"description":"The action list run when action is 'perform-action' (e.g. [{ \"type\": \"start_content\", \"content\": \"\u003cflowId>\" }]). Ignored under 'show-tooltip' — put button actions inside the tooltip content instead.","type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.1.behavior.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.1.behavior.actions","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.1.behavior","typeLabel":"object"}},"additionalProperties":{"oneOf":[{}],"uniqueKey":"data.oneOf.1.additionalProperties","typeLabel":"any"},"uniqueKey":"data.oneOf.1","typeLabel":"object"},{"type":"object","description":"Type-specific body for a non-flow content version: checklist / launcher / banner / tracker / announcement / resource-center. Field-level merged onto the existing data.","properties":{"placement":{"description":"Where the banner shows: the top/bottom of the page, or relative to a container element (top/bottom of it, or immediately before/after it). The container/element variants require `containerTarget`.","type":"string","enum":["top-of-page","bottom-of-page","top-of-container-element","bottom-of-container-element","immediately-before-element","immediately-after-element"],"uniqueKey":"data.oneOf.2.placement","typeLabel":"enum\u003cstring>"},"content":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","markdown"],"properties":{"type":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"markdown":{"type":"string","description":"A small markdown subset: paragraphs, `# `/`## ` headings (h1/h2 only — no h3+), `-`/`*` and `1.` lists, ``` code fences; inline `**bold**`, `*italic*`, `[text](url)`, and `{{ attribute_code | default: \"x\" }}` for user attributes. Anything outside this subset is SILENTLY normalized, not rejected: h3+ → h2; blockquotes flatten to paragraphs; tables, horizontal rules, strikethrough, inline images/code, and liquid filters other than `default` are dropped. Unsupported syntax won't round-trip — don't rely on it.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.0.markdown","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.0.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.0.id","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["image"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.id","typeLabel":"string"},"alt":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.alt","typeLabel":"string"},"link":{"type":"object","properties":{"url":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.link.url","typeLabel":"string"},"newTab":{"type":"boolean","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.link.newTab","typeLabel":"boolean"}},"required":["url"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.link","typeLabel":"object"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.width","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","text"],"properties":{"type":{"type":"string","enum":["button"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"text":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.text","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.id","typeLabel":"string"},"actions":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.actions","typeLabel":"object[]"},"disabledWhen":{"description":"REACTIVE slot — polled live in the browser (the button disables the moment the conditions match). Client-evaluable condition types only: attribute / current_url / element / text_input / text_filled / time_window; event / segment / content_state are rejected (E1017).","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.disabledWhen","typeLabel":"object[]"},"hiddenWhen":{"description":"REACTIVE slot — polled live in the browser (the button shows/hides as conditions change). Same client-evaluable-only rule as `disabledWhen`.","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.hiddenWhen","typeLabel":"object[]"},"variant":{"type":"string","enum":["primary","secondary"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.variant","typeLabel":"enum\u003cstring>"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["embed"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"description":"Paste the page's normal URL (e.g. a youtube.com/watch link) — on write it is resolved through the standard oEmbed provider registry (YouTube, Vimeo, Loom, Figma, …) and the provider's official embed markup is stored, so you never hand-build /embed/ URLs. A URL no provider claims is iframed as-is — it renders only if that site allows being framed (no X-Frame-Options/CSP block).","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3.id","typeLabel":"string"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3.width","typeLabel":"object"},"height":{"description":"Embed height. Optional ONLY for provider embeds (YouTube/Vimeo/… size themselves by aspect ratio); a URL with NO oEmbed provider has no ratio, and omitting a pixel height leaves the iframe at the browser's built-in default — a strip ~150px tall, almost never the intended size (validate warns). For plain-iframe URLs always set `{ \"unit\": \"pixels\", \"value\": … }`.","type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3.height.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3.height.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3.height","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","question"],"properties":{"type":{"type":"string","enum":["question"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"question":{"oneOf":[{"type":"object","required":["kind","name"],"properties":{"kind":{"type":"string","enum":["nps"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.0.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.0.name","typeLabel":"string"},"cvid":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.0.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.0.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.0.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.0.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.0","typeLabel":"object"},{"type":"object","required":["kind","name","style","range"],"properties":{"kind":{"type":"string","enum":["rating"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.1.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.1.name","typeLabel":"string"},"style":{"type":"string","enum":["star","scale"],"description":"star = star rating; scale = a numeric scale. A \"scale\" question IS a rating with style:\"scale\" — there is no separate \"scale\" kind.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.1.style","typeLabel":"enum\u003cstring>"},"range":{"type":"object","properties":{"low":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.1.range.low","typeLabel":"number"},"high":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.1.range.high","typeLabel":"number"}},"required":["low","high"],"description":"Numeric range, e.g. { low: 1, high: 5 }.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.1.range","typeLabel":"object"},"cvid":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.1.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.1.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.1.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.1.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.1","typeLabel":"object"},{"type":"object","required":["kind","name","multiline"],"properties":{"kind":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.2.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.2.name","typeLabel":"string"},"multiline":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.2.multiline","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.2.cvid","typeLabel":"string"},"placeholder":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.2.placeholder","typeLabel":"string"},"buttonText":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.2.buttonText","typeLabel":"string"},"required":{"description":"Require an answer before submit. ONLY `text` supports this — nps / rating / choice cannot be marked required.","type":"boolean","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.2.required","typeLabel":"boolean"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.2.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.2","typeLabel":"object"},{"type":"object","required":["kind","name","options","allowMultiple"],"properties":{"kind":{"type":"string","enum":["choice"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.3.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.3.name","typeLabel":"string"},"options":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].label","typeLabel":"string"},"value":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].value","typeLabel":"string"}},"required":["label","value"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX]","typeLabel":"object"},"description":"Each option has a human-facing `label` and a stored `value` — the `value` is what gets recorded/bound as the answer.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.3.options","typeLabel":"object[]"},"allowMultiple":{"type":"boolean","description":"false = single-select, true = multi-select. A multi-select answer needs a `list`-typed bound attribute.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.3.allowMultiple","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.3.cvid","typeLabel":"string"},"enableOther":{"type":"boolean","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.3.enableOther","typeLabel":"boolean"},"otherPlaceholder":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.3.otherPlaceholder","typeLabel":"string"},"shuffle":{"type":"boolean","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.3.shuffle","typeLabel":"boolean"},"buttonText":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.3.buttonText","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.3.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question.oneOf.3","typeLabel":"object"}],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.question","typeLabel":"object"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.id","typeLabel":"string"},"actions":{"description":"Actions that fire when this question is answered (on pick for nps/rating/single-select; on its Submit button for text/multi-select). Put a `goto_step` HERE to advance to the next step — without it the question records the answer but the flow does NOT advance (validate flags the next step \"not reachable\"). Do NOT add a separate `button` block just to advance: it doubles up with the question's own submit affordance.","type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4.actions","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Lays 2+ blocks out side by side in one row. Top-level `content` blocks otherwise stack vertically — each is its own full-width row — so two `button` blocks placed directly in `content` render as two stacked rows, NOT a button pair. For a modal/tooltip footer with a Skip + primary button: one `columns` block with two `{ width: { unit: \"fill\" }, blocks: [...] }` entries, one button per column.","required":["type","columns"],"properties":{"type":{"type":"string","enum":["columns"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"columns":{"type":"array","items":{"type":"object","properties":{"blocks":{"type":"array","items":{"$circularRef":"b55c69d5-0b98-4319-a584-ae0961850a0b"},"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.columns.items.[INDEX].blocks","typeLabel":"array"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels","fill"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.columns.items.[INDEX].width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.columns.items.[INDEX].width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.columns.items.[INDEX].width","typeLabel":"object"},"justify":{"description":"Horizontal alignment of this column's own block(s) within the column's width — use \"end\"/\"center\" to right-align or center a lone button (a single-column `columns` block).","type":"string","enum":["start","center","end","between","around","evenly"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.columns.items.[INDEX].justify","typeLabel":"enum\u003cstring>"},"align":{"type":"string","enum":["start","center","end","baseline"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.columns.items.[INDEX].align","typeLabel":"enum\u003cstring>"},"padding":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding","typeLabel":"object"}},"required":["blocks"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.columns.items.[INDEX]","typeLabel":"object"},"description":"One entry per column, laid out left-to-right. Each column is a mini vertical stack of `blocks` (usually one).","isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.columns","typeLabel":"object[]"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5.id","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.6.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.6.id","typeLabel":"string"},"note":{"type":"string","uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.6.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.2.content.items.[INDEX].oneOf.6","typeLabel":"object"}],"uniqueKey":"data.oneOf.2.content.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.2.content","typeLabel":"object[]"},"zIndex":{"description":"Stacking order (CSS z-index). Integer; negative values are allowed. Omit to use the SDK's default stacking.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"uniqueKey":"data.oneOf.2.zIndex","typeLabel":"integer"},"settings":{"type":"object","properties":{"overlayOverAppContent":{"description":"When true the banner floats over the page; when false it takes its own space and pushes the page content down. Push mode only displaces normal-flow content — the host app's `position: fixed` bars (top nav, sidebars) do NOT move and will overlap the banner. For such apps, either float the banner over the content (true), or use a container/element-relative `placement` so the banner lives inside the scrolling content area instead of fighting the fixed bars. Floating alone does NOT keep the banner on screen — it still scrolls away with the page (CSS absolute); pair it with `stickToTop` for the always-visible floating bar (see the 2x2 there).","type":"boolean","uniqueKey":"data.oneOf.2.settings.overlayOverAppContent","typeLabel":"boolean"},"stickToTop":{"description":"Pins the banner to the viewport edge while the user scrolls — the EDGE follows `placement`, despite the name: top variants pin to the top, bottom variants to the BOTTOM. Combines with `overlayOverAppContent` into four modes (CSS position): neither = in-flow, scrolls away with the page (the default); stick only = keeps its own space and pins (sticky); overlay only = floats but STILL scrolls away (absolute); both = floats and stays pinned (fixed) — the classic always-visible bar. So for \"visible while scrolling\", set this true regardless of overlay.","type":"boolean","uniqueKey":"data.oneOf.2.settings.stickToTop","typeLabel":"boolean"},"allowDismiss":{"description":"Adds an X button so the user can permanently dismiss the banner.","type":"boolean","uniqueKey":"data.oneOf.2.settings.allowDismiss","typeLabel":"boolean"},"animateOnAppear":{"description":"Slide the banner in instead of popping into place.","type":"boolean","uniqueKey":"data.oneOf.2.settings.animateOnAppear","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.2.settings","typeLabel":"object"},"containerTarget":{"description":"The anchor element for the container/element-relative `placement` variants (top/bottom of it, or immediately before/after it). Required by those placements; ignored for top/bottom-of-page.","type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.2.containerTarget.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.2.containerTarget.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.2.containerTarget.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.2.containerTarget","typeLabel":"object"},"layout":{"type":"object","properties":{"maxContentWidth":{"description":"Max width of the banner content, in pixels. Omit for no limit.","type":"number","uniqueKey":"data.oneOf.2.layout.maxContentWidth","typeLabel":"number"},"maxEmbedWidth":{"description":"Max width of the embed container, in pixels. Omit for no limit.","type":"number","uniqueKey":"data.oneOf.2.layout.maxEmbedWidth","typeLabel":"number"},"borderRadius":{"description":"Corner rounding in pixels. Omit for the theme default.","type":"number","uniqueKey":"data.oneOf.2.layout.borderRadius","typeLabel":"number"},"outerMargin":{"description":"Space (in pixels) around the banner on all four sides.","type":"object","properties":{"top":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.2.layout.outerMargin.top","typeLabel":"number"},"right":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.2.layout.outerMargin.right","typeLabel":"number"},"bottom":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.2.layout.outerMargin.bottom","typeLabel":"number"},"left":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.2.layout.outerMargin.left","typeLabel":"number"}},"required":["top","right","bottom","left"],"uniqueKey":"data.oneOf.2.layout.outerMargin","typeLabel":"object"}},"uniqueKey":"data.oneOf.2.layout","typeLabel":"object"}},"additionalProperties":{"oneOf":[{}],"uniqueKey":"data.oneOf.2.additionalProperties","typeLabel":"any"},"uniqueKey":"data.oneOf.2","typeLabel":"object"},{"type":"object","description":"Type-specific body for a non-flow content version: checklist / launcher / banner / tracker / announcement / resource-center. Field-level merged onto the existing data.","required":["event"],"properties":{"event":{"oneOf":[{"type":"string","description":"The CUSTOM event this tracker fires when its startRules match. A custom event's codeName (preferred) or id from the event definitions list — accepted either way, stored and returned as the codeName. MUST be a custom event: built-in / system (predefined) events are rejected (a tracker can only fire custom events; create one a custom event definition you created). A tracker is headless — no UI, no theme; it just fires this event whenever its startRules trigger conditions are met. null when unset.","isRequired":true,"uniqueKey":"data.oneOf.3.event.oneOf.0","typeLabel":"string"},{"type":"null","description":"The CUSTOM event this tracker fires when its startRules match. A custom event's codeName (preferred) or id from the event definitions list — accepted either way, stored and returned as the codeName. MUST be a custom event: built-in / system (predefined) events are rejected (a tracker can only fire custom events; create one a custom event definition you created). A tracker is headless — no UI, no theme; it just fires this event whenever its startRules trigger conditions are met. null when unset.","isRequired":true,"uniqueKey":"data.oneOf.3.event.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"data.oneOf.3.event","typeLabel":"string | null"}},"additionalProperties":{"oneOf":[{}],"uniqueKey":"data.oneOf.3.additionalProperties","typeLabel":"any"},"uniqueKey":"data.oneOf.3","typeLabel":"object"},{"type":"object","description":"Type-specific body for a non-flow content version: checklist / launcher / banner / tracker / announcement / resource-center. Field-level merged onto the existing data.","properties":{"title":{"description":"Title shown in the feed row and the detail view. Required to publish — an untitled announcement would render a blank row. Seeded from the content's `name` at create, then INDEPENDENT: renaming the content later does not update the title (and events/analytics label by the content name, not this title). Plain string — NO `{{ }}` interpolation (braces would render literally). For a personalized body use the intro/detail content blocks.","type":"string","uniqueKey":"data.oneOf.4.title","typeLabel":"string"},"introContent":{"description":"Rich content shown in the feed row (and in the popup, for `distribution: \"popup\"`). Same block vocabulary as flow steps MINUS questions: text / image / button / embed / columns. Button actions here: start_content / navigate / run_javascript only — no dismiss (feed items are marked seen, not dismissed) and no goto_step.","type":"array","items":{"oneOf":[{"type":"object","required":["type","markdown"],"properties":{"type":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"markdown":{"type":"string","description":"A small markdown subset: paragraphs, `# `/`## ` headings (h1/h2 only — no h3+), `-`/`*` and `1.` lists, ``` code fences; inline `**bold**`, `*italic*`, `[text](url)`, and `{{ attribute_code | default: \"x\" }}` for user attributes. Anything outside this subset is SILENTLY normalized, not rejected: h3+ → h2; blockquotes flatten to paragraphs; tables, horizontal rules, strikethrough, inline images/code, and liquid filters other than `default` are dropped. Unsupported syntax won't round-trip — don't rely on it.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.0.markdown","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.0.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.0.id","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["image"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.id","typeLabel":"string"},"alt":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.alt","typeLabel":"string"},"link":{"type":"object","properties":{"url":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.link.url","typeLabel":"string"},"newTab":{"type":"boolean","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.link.newTab","typeLabel":"boolean"}},"required":["url"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.link","typeLabel":"object"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.width","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","text"],"properties":{"type":{"type":"string","enum":["button"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"text":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.text","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.id","typeLabel":"string"},"actions":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.actions","typeLabel":"object[]"},"disabledWhen":{"description":"REACTIVE slot — polled live in the browser (the button disables the moment the conditions match). Client-evaluable condition types only: attribute / current_url / element / text_input / text_filled / time_window; event / segment / content_state are rejected (E1017).","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.disabledWhen","typeLabel":"object[]"},"hiddenWhen":{"description":"REACTIVE slot — polled live in the browser (the button shows/hides as conditions change). Same client-evaluable-only rule as `disabledWhen`.","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.hiddenWhen","typeLabel":"object[]"},"variant":{"type":"string","enum":["primary","secondary"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.variant","typeLabel":"enum\u003cstring>"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["embed"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"description":"Paste the page's normal URL (e.g. a youtube.com/watch link) — on write it is resolved through the standard oEmbed provider registry (YouTube, Vimeo, Loom, Figma, …) and the provider's official embed markup is stored, so you never hand-build /embed/ URLs. A URL no provider claims is iframed as-is — it renders only if that site allows being framed (no X-Frame-Options/CSP block).","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3.id","typeLabel":"string"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3.width","typeLabel":"object"},"height":{"description":"Embed height. Optional ONLY for provider embeds (YouTube/Vimeo/… size themselves by aspect ratio); a URL with NO oEmbed provider has no ratio, and omitting a pixel height leaves the iframe at the browser's built-in default — a strip ~150px tall, almost never the intended size (validate warns). For plain-iframe URLs always set `{ \"unit\": \"pixels\", \"value\": … }`.","type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3.height.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3.height.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3.height","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","question"],"properties":{"type":{"type":"string","enum":["question"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"question":{"oneOf":[{"type":"object","required":["kind","name"],"properties":{"kind":{"type":"string","enum":["nps"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.0.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.0.name","typeLabel":"string"},"cvid":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.0.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.0.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.0.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.0.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.0","typeLabel":"object"},{"type":"object","required":["kind","name","style","range"],"properties":{"kind":{"type":"string","enum":["rating"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.1.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.1.name","typeLabel":"string"},"style":{"type":"string","enum":["star","scale"],"description":"star = star rating; scale = a numeric scale. A \"scale\" question IS a rating with style:\"scale\" — there is no separate \"scale\" kind.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.1.style","typeLabel":"enum\u003cstring>"},"range":{"type":"object","properties":{"low":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.1.range.low","typeLabel":"number"},"high":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.1.range.high","typeLabel":"number"}},"required":["low","high"],"description":"Numeric range, e.g. { low: 1, high: 5 }.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.1.range","typeLabel":"object"},"cvid":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.1.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.1.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.1.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.1.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.1","typeLabel":"object"},{"type":"object","required":["kind","name","multiline"],"properties":{"kind":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.2.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.2.name","typeLabel":"string"},"multiline":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.2.multiline","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.2.cvid","typeLabel":"string"},"placeholder":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.2.placeholder","typeLabel":"string"},"buttonText":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.2.buttonText","typeLabel":"string"},"required":{"description":"Require an answer before submit. ONLY `text` supports this — nps / rating / choice cannot be marked required.","type":"boolean","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.2.required","typeLabel":"boolean"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.2.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.2","typeLabel":"object"},{"type":"object","required":["kind","name","options","allowMultiple"],"properties":{"kind":{"type":"string","enum":["choice"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.3.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.3.name","typeLabel":"string"},"options":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].label","typeLabel":"string"},"value":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].value","typeLabel":"string"}},"required":["label","value"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX]","typeLabel":"object"},"description":"Each option has a human-facing `label` and a stored `value` — the `value` is what gets recorded/bound as the answer.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.3.options","typeLabel":"object[]"},"allowMultiple":{"type":"boolean","description":"false = single-select, true = multi-select. A multi-select answer needs a `list`-typed bound attribute.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.3.allowMultiple","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.3.cvid","typeLabel":"string"},"enableOther":{"type":"boolean","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.3.enableOther","typeLabel":"boolean"},"otherPlaceholder":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.3.otherPlaceholder","typeLabel":"string"},"shuffle":{"type":"boolean","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.3.shuffle","typeLabel":"boolean"},"buttonText":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.3.buttonText","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.3.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question.oneOf.3","typeLabel":"object"}],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.question","typeLabel":"object"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.id","typeLabel":"string"},"actions":{"description":"Actions that fire when this question is answered (on pick for nps/rating/single-select; on its Submit button for text/multi-select). Put a `goto_step` HERE to advance to the next step — without it the question records the answer but the flow does NOT advance (validate flags the next step \"not reachable\"). Do NOT add a separate `button` block just to advance: it doubles up with the question's own submit affordance.","type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4.actions","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Lays 2+ blocks out side by side in one row. Top-level `content` blocks otherwise stack vertically — each is its own full-width row — so two `button` blocks placed directly in `content` render as two stacked rows, NOT a button pair. For a modal/tooltip footer with a Skip + primary button: one `columns` block with two `{ width: { unit: \"fill\" }, blocks: [...] }` entries, one button per column.","required":["type","columns"],"properties":{"type":{"type":"string","enum":["columns"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"columns":{"type":"array","items":{"type":"object","properties":{"blocks":{"type":"array","items":{"$circularRef":"b55c69d5-0b98-4319-a584-ae0961850a0b"},"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.columns.items.[INDEX].blocks","typeLabel":"array"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels","fill"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.columns.items.[INDEX].width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.columns.items.[INDEX].width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.columns.items.[INDEX].width","typeLabel":"object"},"justify":{"description":"Horizontal alignment of this column's own block(s) within the column's width — use \"end\"/\"center\" to right-align or center a lone button (a single-column `columns` block).","type":"string","enum":["start","center","end","between","around","evenly"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.columns.items.[INDEX].justify","typeLabel":"enum\u003cstring>"},"align":{"type":"string","enum":["start","center","end","baseline"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.columns.items.[INDEX].align","typeLabel":"enum\u003cstring>"},"padding":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.columns.items.[INDEX].padding","typeLabel":"object"}},"required":["blocks"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.columns.items.[INDEX]","typeLabel":"object"},"description":"One entry per column, laid out left-to-right. Each column is a mini vertical stack of `blocks` (usually one).","isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.columns","typeLabel":"object[]"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5.id","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.6.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.6.id","typeLabel":"string"},"note":{"type":"string","uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.6.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.introContent.items.[INDEX].oneOf.6","typeLabel":"object"}],"uniqueKey":"data.oneOf.4.introContent.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.4.introContent","typeLabel":"object[]"},"enableReadMore":{"description":"Adds a \"Read more\" button that opens a detail page rendered from `detailContent`.","type":"boolean","uniqueKey":"data.oneOf.4.enableReadMore","typeLabel":"boolean"},"readMoreLabel":{"description":"Label of the \"Read more\" button (default \"Read more\").","type":"string","uniqueKey":"data.oneOf.4.readMoreLabel","typeLabel":"string"},"detailContent":{"description":"Full content of the \"Read more\" detail page — only rendered when `enableReadMore` is true. Same block rules as `introContent`.","type":"array","items":{"oneOf":[{"type":"object","required":["type","markdown"],"properties":{"type":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"markdown":{"type":"string","description":"A small markdown subset: paragraphs, `# `/`## ` headings (h1/h2 only — no h3+), `-`/`*` and `1.` lists, ``` code fences; inline `**bold**`, `*italic*`, `[text](url)`, and `{{ attribute_code | default: \"x\" }}` for user attributes. Anything outside this subset is SILENTLY normalized, not rejected: h3+ → h2; blockquotes flatten to paragraphs; tables, horizontal rules, strikethrough, inline images/code, and liquid filters other than `default` are dropped. Unsupported syntax won't round-trip — don't rely on it.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.0.markdown","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.0.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.0.id","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["image"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.id","typeLabel":"string"},"alt":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.alt","typeLabel":"string"},"link":{"type":"object","properties":{"url":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.link.url","typeLabel":"string"},"newTab":{"type":"boolean","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.link.newTab","typeLabel":"boolean"}},"required":["url"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.link","typeLabel":"object"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.width","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","text"],"properties":{"type":{"type":"string","enum":["button"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"text":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.text","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.id","typeLabel":"string"},"actions":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.actions","typeLabel":"object[]"},"disabledWhen":{"description":"REACTIVE slot — polled live in the browser (the button disables the moment the conditions match). Client-evaluable condition types only: attribute / current_url / element / text_input / text_filled / time_window; event / segment / content_state are rejected (E1017).","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.disabledWhen","typeLabel":"object[]"},"hiddenWhen":{"description":"REACTIVE slot — polled live in the browser (the button shows/hides as conditions change). Same client-evaluable-only rule as `disabledWhen`.","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.hiddenWhen","typeLabel":"object[]"},"variant":{"type":"string","enum":["primary","secondary"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.variant","typeLabel":"enum\u003cstring>"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["embed"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"description":"Paste the page's normal URL (e.g. a youtube.com/watch link) — on write it is resolved through the standard oEmbed provider registry (YouTube, Vimeo, Loom, Figma, …) and the provider's official embed markup is stored, so you never hand-build /embed/ URLs. A URL no provider claims is iframed as-is — it renders only if that site allows being framed (no X-Frame-Options/CSP block).","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3.id","typeLabel":"string"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3.width","typeLabel":"object"},"height":{"description":"Embed height. Optional ONLY for provider embeds (YouTube/Vimeo/… size themselves by aspect ratio); a URL with NO oEmbed provider has no ratio, and omitting a pixel height leaves the iframe at the browser's built-in default — a strip ~150px tall, almost never the intended size (validate warns). For plain-iframe URLs always set `{ \"unit\": \"pixels\", \"value\": … }`.","type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3.height.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3.height.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3.height","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","question"],"properties":{"type":{"type":"string","enum":["question"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"question":{"oneOf":[{"type":"object","required":["kind","name"],"properties":{"kind":{"type":"string","enum":["nps"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.0.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.0.name","typeLabel":"string"},"cvid":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.0.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.0.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.0.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.0.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.0","typeLabel":"object"},{"type":"object","required":["kind","name","style","range"],"properties":{"kind":{"type":"string","enum":["rating"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.1.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.1.name","typeLabel":"string"},"style":{"type":"string","enum":["star","scale"],"description":"star = star rating; scale = a numeric scale. A \"scale\" question IS a rating with style:\"scale\" — there is no separate \"scale\" kind.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.1.style","typeLabel":"enum\u003cstring>"},"range":{"type":"object","properties":{"low":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.1.range.low","typeLabel":"number"},"high":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.1.range.high","typeLabel":"number"}},"required":["low","high"],"description":"Numeric range, e.g. { low: 1, high: 5 }.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.1.range","typeLabel":"object"},"cvid":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.1.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.1.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.1.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.1.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.1","typeLabel":"object"},{"type":"object","required":["kind","name","multiline"],"properties":{"kind":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.2.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.2.name","typeLabel":"string"},"multiline":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.2.multiline","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.2.cvid","typeLabel":"string"},"placeholder":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.2.placeholder","typeLabel":"string"},"buttonText":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.2.buttonText","typeLabel":"string"},"required":{"description":"Require an answer before submit. ONLY `text` supports this — nps / rating / choice cannot be marked required.","type":"boolean","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.2.required","typeLabel":"boolean"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.2.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.2","typeLabel":"object"},{"type":"object","required":["kind","name","options","allowMultiple"],"properties":{"kind":{"type":"string","enum":["choice"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.3.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.3.name","typeLabel":"string"},"options":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].label","typeLabel":"string"},"value":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].value","typeLabel":"string"}},"required":["label","value"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX]","typeLabel":"object"},"description":"Each option has a human-facing `label` and a stored `value` — the `value` is what gets recorded/bound as the answer.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.3.options","typeLabel":"object[]"},"allowMultiple":{"type":"boolean","description":"false = single-select, true = multi-select. A multi-select answer needs a `list`-typed bound attribute.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.3.allowMultiple","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.3.cvid","typeLabel":"string"},"enableOther":{"type":"boolean","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.3.enableOther","typeLabel":"boolean"},"otherPlaceholder":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.3.otherPlaceholder","typeLabel":"string"},"shuffle":{"type":"boolean","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.3.shuffle","typeLabel":"boolean"},"buttonText":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.3.buttonText","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.3.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question.oneOf.3","typeLabel":"object"}],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.question","typeLabel":"object"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.id","typeLabel":"string"},"actions":{"description":"Actions that fire when this question is answered (on pick for nps/rating/single-select; on its Submit button for text/multi-select). Put a `goto_step` HERE to advance to the next step — without it the question records the answer but the flow does NOT advance (validate flags the next step \"not reachable\"). Do NOT add a separate `button` block just to advance: it doubles up with the question's own submit affordance.","type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4.actions","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Lays 2+ blocks out side by side in one row. Top-level `content` blocks otherwise stack vertically — each is its own full-width row — so two `button` blocks placed directly in `content` render as two stacked rows, NOT a button pair. For a modal/tooltip footer with a Skip + primary button: one `columns` block with two `{ width: { unit: \"fill\" }, blocks: [...] }` entries, one button per column.","required":["type","columns"],"properties":{"type":{"type":"string","enum":["columns"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"columns":{"type":"array","items":{"type":"object","properties":{"blocks":{"type":"array","items":{"$circularRef":"b55c69d5-0b98-4319-a584-ae0961850a0b"},"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.columns.items.[INDEX].blocks","typeLabel":"array"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels","fill"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.columns.items.[INDEX].width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.columns.items.[INDEX].width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.columns.items.[INDEX].width","typeLabel":"object"},"justify":{"description":"Horizontal alignment of this column's own block(s) within the column's width — use \"end\"/\"center\" to right-align or center a lone button (a single-column `columns` block).","type":"string","enum":["start","center","end","between","around","evenly"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.columns.items.[INDEX].justify","typeLabel":"enum\u003cstring>"},"align":{"type":"string","enum":["start","center","end","baseline"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.columns.items.[INDEX].align","typeLabel":"enum\u003cstring>"},"padding":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.columns.items.[INDEX].padding","typeLabel":"object"}},"required":["blocks"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.columns.items.[INDEX]","typeLabel":"object"},"description":"One entry per column, laid out left-to-right. Each column is a mini vertical stack of `blocks` (usually one).","isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.columns","typeLabel":"object[]"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5.id","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.6.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.6.id","typeLabel":"string"},"note":{"type":"string","uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.6.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX].oneOf.6","typeLabel":"object"}],"uniqueKey":"data.oneOf.4.detailContent.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.4.detailContent","typeLabel":"object[]"},"distribution":{"description":"How loudly users are notified: `silent` = appears in the feed only; `badge` (default) = feed + unread-count badge on the resource-center launcher; `popup` = feed + badge + actively presented ONCE to each user (style per `popupConfig`). Only the newest unseen popup self-presents; it never re-shows after being seen.","type":"string","enum":["silent","badge","popup"],"uniqueKey":"data.oneOf.4.distribution","typeLabel":"enum\u003cstring>"},"popupConfig":{"description":"Popup presentation — only USED when `distribution` is `popup` (stored and echoed under other distributions, taking effect if distribution later switches to popup). Omit for the default (bubble).","type":"object","properties":{"style":{"type":"string","enum":["modal","bubble"],"description":"`modal` = centered modal with overlay; `bubble` = speech bubble anchored at the resource-center launcher.","isRequired":true,"uniqueKey":"data.oneOf.4.popupConfig.style","typeLabel":"enum\u003cstring>"}},"required":["style"],"uniqueKey":"data.oneOf.4.popupConfig","typeLabel":"object"}},"additionalProperties":{"oneOf":[{}],"uniqueKey":"data.oneOf.4.additionalProperties","typeLabel":"any"},"uniqueKey":"data.oneOf.4","typeLabel":"object"},{"type":"object","description":"Type-specific body for a non-flow content version: checklist / launcher / banner / tracker / announcement / resource-center. Field-level merged onto the existing data.","properties":{"buttonText":{"description":"Text on the floating resource-center launcher (only rendered when the theme launcher is in text mode). Plain string — NO `{{ }}` interpolation.","type":"string","uniqueKey":"data.oneOf.5.buttonText","typeLabel":"string"},"headerText":{"description":"Panel header title — but NOT on the default home view: with a styled theme header (resourceCenter.headerBackground other than \"none\", the hero look) the home view shows the logo + background and this text does not render there. It renders on the home view only under the compact \"none\" header style, and on SUB-pages as the fallback title (when the tab/page has no name). For a visible home greeting use a `richtext` block. Plain string — NO `{{ }}` interpolation (braces would render literally).","type":"string","uniqueKey":"data.oneOf.5.headerText","typeLabel":"string"},"tabs":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"Tab label. Plain text — NO `{{ }}` interpolation (braces would render literally).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].name","typeLabel":"string"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].id","typeLabel":"string"},"icon":{"type":"object","properties":{"source":{"description":"Icon source. 'builtin' = a named icon from the bundled RemixIcon set (see `type`); 'upload'/'url' = a custom image via `url`; 'none' = no icon.","type":"string","enum":["none","builtin","upload","url"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].icon.source","typeLabel":"enum\u003cstring>"},"type":{"description":"Builtin icon name (when source='builtin'): a RemixIcon name in kebab `-line`/`-fill` style — e.g. `home-line`, `question-line`, `chat-line`, `settings-line`, `rocket`. NOT lucide names: `help-circle` / `sparkles` / `book-open` / `message-circle` are not in the set — validate warns on an unregistered name (the icon slot renders empty; the block itself still shows). Unsure of a name? Use source='none' rather than guess. Common names + an intent→name table are in the MCP authoring guide (icons section).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].icon.type","typeLabel":"string"},"url":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].icon.url","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].icon","typeLabel":"object"},"blocks":{"default":[],"description":"Tab blocks use the resource-center vocabulary — richtext / divider / action / sub-page / content-list / live-chat / announcement — NOT the flow content blocks. Put text inside a `richtext` block: { \"type\": \"richtext\", \"content\": [{ \"object\": \"block\", \"type\": \"text\", \"markdown\": \"…\" }] }. A bare top-level text block (type \"text\") is rejected here. At most one `announcement` block per resource center (across all tabs).","type":"array","items":{"oneOf":[{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["richtext"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.id","typeLabel":"string"},"onlyShowWhen":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.onlyShowWhen","typeLabel":"object[]"},"name":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.name","typeLabel":"string"},"content":{"default":[],"type":"array","items":{"oneOf":[{"type":"object","required":["type","markdown"],"properties":{"type":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"markdown":{"type":"string","description":"A small markdown subset: paragraphs, `# `/`## ` headings (h1/h2 only — no h3+), `-`/`*` and `1.` lists, ``` code fences; inline `**bold**`, `*italic*`, `[text](url)`, and `{{ attribute_code | default: \"x\" }}` for user attributes. Anything outside this subset is SILENTLY normalized, not rejected: h3+ → h2; blockquotes flatten to paragraphs; tables, horizontal rules, strikethrough, inline images/code, and liquid filters other than `default` are dropped. Unsupported syntax won't round-trip — don't rely on it.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.0.markdown","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.0.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.0.id","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["image"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.id","typeLabel":"string"},"alt":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.alt","typeLabel":"string"},"link":{"type":"object","properties":{"url":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.link.url","typeLabel":"string"},"newTab":{"type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.link.newTab","typeLabel":"boolean"}},"required":["url"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.link","typeLabel":"object"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.width","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","text"],"properties":{"type":{"type":"string","enum":["button"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"text":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.text","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.id","typeLabel":"string"},"actions":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.actions","typeLabel":"object[]"},"disabledWhen":{"description":"REACTIVE slot — polled live in the browser (the button disables the moment the conditions match). Client-evaluable condition types only: attribute / current_url / element / text_input / text_filled / time_window; event / segment / content_state are rejected (E1017).","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"type":"any","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"any"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where","typeLabel":"any[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.disabledWhen","typeLabel":"object[]"},"hiddenWhen":{"description":"REACTIVE slot — polled live in the browser (the button shows/hides as conditions change). Same client-evaluable-only rule as `disabledWhen`.","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"type":"any","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"any"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where","typeLabel":"any[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.hiddenWhen","typeLabel":"object[]"},"variant":{"type":"string","enum":["primary","secondary"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.variant","typeLabel":"enum\u003cstring>"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["embed"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"description":"Paste the page's normal URL (e.g. a youtube.com/watch link) — on write it is resolved through the standard oEmbed provider registry (YouTube, Vimeo, Loom, Figma, …) and the provider's official embed markup is stored, so you never hand-build /embed/ URLs. A URL no provider claims is iframed as-is — it renders only if that site allows being framed (no X-Frame-Options/CSP block).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3.id","typeLabel":"string"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3.width","typeLabel":"object"},"height":{"description":"Embed height. Optional ONLY for provider embeds (YouTube/Vimeo/… size themselves by aspect ratio); a URL with NO oEmbed provider has no ratio, and omitting a pixel height leaves the iframe at the browser's built-in default — a strip ~150px tall, almost never the intended size (validate warns). For plain-iframe URLs always set `{ \"unit\": \"pixels\", \"value\": … }`.","type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3.height.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3.height.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3.height","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","question"],"properties":{"type":{"type":"string","enum":["question"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"question":{"oneOf":[{"type":"object","required":["kind","name"],"properties":{"kind":{"type":"string","enum":["nps"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.0.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.0.name","typeLabel":"string"},"cvid":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.0.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.0.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.0.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.0.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.0","typeLabel":"object"},{"type":"object","required":["kind","name","style","range"],"properties":{"kind":{"type":"string","enum":["rating"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.name","typeLabel":"string"},"style":{"type":"string","enum":["star","scale"],"description":"star = star rating; scale = a numeric scale. A \"scale\" question IS a rating with style:\"scale\" — there is no separate \"scale\" kind.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.style","typeLabel":"enum\u003cstring>"},"range":{"type":"object","properties":{"low":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.range.low","typeLabel":"number"},"high":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.range.high","typeLabel":"number"}},"required":["low","high"],"description":"Numeric range, e.g. { low: 1, high: 5 }.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.range","typeLabel":"object"},"cvid":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.1","typeLabel":"object"},{"type":"object","required":["kind","name","multiline"],"properties":{"kind":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2.name","typeLabel":"string"},"multiline":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2.multiline","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2.cvid","typeLabel":"string"},"placeholder":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2.placeholder","typeLabel":"string"},"buttonText":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2.buttonText","typeLabel":"string"},"required":{"description":"Require an answer before submit. ONLY `text` supports this — nps / rating / choice cannot be marked required.","type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2.required","typeLabel":"boolean"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.2","typeLabel":"object"},{"type":"object","required":["kind","name","options","allowMultiple"],"properties":{"kind":{"type":"string","enum":["choice"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.name","typeLabel":"string"},"options":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].label","typeLabel":"string"},"value":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].value","typeLabel":"string"}},"required":["label","value"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX]","typeLabel":"object"},"description":"Each option has a human-facing `label` and a stored `value` — the `value` is what gets recorded/bound as the answer.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.options","typeLabel":"object[]"},"allowMultiple":{"type":"boolean","description":"false = single-select, true = multi-select. A multi-select answer needs a `list`-typed bound attribute.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.allowMultiple","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.cvid","typeLabel":"string"},"enableOther":{"type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.enableOther","typeLabel":"boolean"},"otherPlaceholder":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.otherPlaceholder","typeLabel":"string"},"shuffle":{"type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.shuffle","typeLabel":"boolean"},"buttonText":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.buttonText","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question.oneOf.3","typeLabel":"object"}],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.question","typeLabel":"object"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.id","typeLabel":"string"},"actions":{"description":"Actions that fire when this question is answered (on pick for nps/rating/single-select; on its Submit button for text/multi-select). Put a `goto_step` HERE to advance to the next step — without it the question records the answer but the flow does NOT advance (validate flags the next step \"not reachable\"). Do NOT add a separate `button` block just to advance: it doubles up with the question's own submit affordance.","type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4.actions","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Lays 2+ blocks out side by side in one row. Top-level `content` blocks otherwise stack vertically — each is its own full-width row — so two `button` blocks placed directly in `content` render as two stacked rows, NOT a button pair. For a modal/tooltip footer with a Skip + primary button: one `columns` block with two `{ width: { unit: \"fill\" }, blocks: [...] }` entries, one button per column.","required":["type","columns"],"properties":{"type":{"type":"string","enum":["columns"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"columns":{"type":"array","items":{"type":"object","properties":{"blocks":{"type":"array","items":{"$circularRef":"b55c69d5-0b98-4319-a584-ae0961850a0b"},"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].blocks","typeLabel":"array"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels","fill"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].width","typeLabel":"object"},"justify":{"description":"Horizontal alignment of this column's own block(s) within the column's width — use \"end\"/\"center\" to right-align or center a lone button (a single-column `columns` block).","type":"string","enum":["start","center","end","between","around","evenly"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].justify","typeLabel":"enum\u003cstring>"},"align":{"type":"string","enum":["start","center","end","baseline"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].align","typeLabel":"enum\u003cstring>"},"padding":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding","typeLabel":"object"}},"required":["blocks"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.columns.items.[INDEX]","typeLabel":"object"},"description":"One entry per column, laid out left-to-right. Each column is a mini vertical stack of `blocks` (usually one).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.columns","typeLabel":"object[]"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5.id","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.6.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.6.id","typeLabel":"string"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.6.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX].oneOf.6","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0.content","typeLabel":"object[]"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["divider"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.id","typeLabel":"string"},"onlyShowWhen":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.onlyShowWhen","typeLabel":"object[]"},"name":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1.name","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","name"],"properties":{"type":{"type":"string","enum":["action"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"Visible label of this block row. Supports `{{ attribute_code | default: \"x\" }}` user-attribute interpolation.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.name","typeLabel":"string"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.id","typeLabel":"string"},"onlyShowWhen":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.onlyShowWhen","typeLabel":"object[]"},"icon":{"type":"object","properties":{"source":{"description":"Icon source. 'builtin' = a named icon from the bundled RemixIcon set (see `type`); 'upload'/'url' = a custom image via `url`; 'none' = no icon.","type":"string","enum":["none","builtin","upload","url"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.icon.source","typeLabel":"enum\u003cstring>"},"type":{"description":"Builtin icon name (when source='builtin'): a RemixIcon name in kebab `-line`/`-fill` style — e.g. `home-line`, `question-line`, `chat-line`, `settings-line`, `rocket`. NOT lucide names: `help-circle` / `sparkles` / `book-open` / `message-circle` are not in the set — validate warns on an unregistered name (the icon slot renders empty; the block itself still shows). Unsure of a name? Use source='none' rather than guess. Common names + an intent→name table are in the MCP authoring guide (icons section).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.icon.type","typeLabel":"string"},"url":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.icon.url","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.icon","typeLabel":"object"},"clickActions":{"default":[],"type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2.clickActions","typeLabel":"object[]"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","name"],"properties":{"type":{"type":"string","enum":["sub-page"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"Visible label of this block row. Supports `{{ attribute_code | default: \"x\" }}` user-attribute interpolation.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.name","typeLabel":"string"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.id","typeLabel":"string"},"onlyShowWhen":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.onlyShowWhen","typeLabel":"object[]"},"icon":{"type":"object","properties":{"source":{"description":"Icon source. 'builtin' = a named icon from the bundled RemixIcon set (see `type`); 'upload'/'url' = a custom image via `url`; 'none' = no icon.","type":"string","enum":["none","builtin","upload","url"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.icon.source","typeLabel":"enum\u003cstring>"},"type":{"description":"Builtin icon name (when source='builtin'): a RemixIcon name in kebab `-line`/`-fill` style — e.g. `home-line`, `question-line`, `chat-line`, `settings-line`, `rocket`. NOT lucide names: `help-circle` / `sparkles` / `book-open` / `message-circle` are not in the set — validate warns on an unregistered name (the icon slot renders empty; the block itself still shows). Unsure of a name? Use source='none' rather than guess. Common names + an intent→name table are in the MCP authoring guide (icons section).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.icon.type","typeLabel":"string"},"url":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.icon.url","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.icon","typeLabel":"object"},"content":{"default":[],"type":"array","items":{"oneOf":[{"type":"object","required":["type","markdown"],"properties":{"type":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"markdown":{"type":"string","description":"A small markdown subset: paragraphs, `# `/`## ` headings (h1/h2 only — no h3+), `-`/`*` and `1.` lists, ``` code fences; inline `**bold**`, `*italic*`, `[text](url)`, and `{{ attribute_code | default: \"x\" }}` for user attributes. Anything outside this subset is SILENTLY normalized, not rejected: h3+ → h2; blockquotes flatten to paragraphs; tables, horizontal rules, strikethrough, inline images/code, and liquid filters other than `default` are dropped. Unsupported syntax won't round-trip — don't rely on it.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.0.markdown","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.0.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.0.id","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["image"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.id","typeLabel":"string"},"alt":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.alt","typeLabel":"string"},"link":{"type":"object","properties":{"url":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.link.url","typeLabel":"string"},"newTab":{"type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.link.newTab","typeLabel":"boolean"}},"required":["url"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.link","typeLabel":"object"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.width","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","text"],"properties":{"type":{"type":"string","enum":["button"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"text":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.text","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.id","typeLabel":"string"},"actions":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.actions","typeLabel":"object[]"},"disabledWhen":{"description":"REACTIVE slot — polled live in the browser (the button disables the moment the conditions match). Client-evaluable condition types only: attribute / current_url / element / text_input / text_filled / time_window; event / segment / content_state are rejected (E1017).","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"type":"any","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"any"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where","typeLabel":"any[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.disabledWhen","typeLabel":"object[]"},"hiddenWhen":{"description":"REACTIVE slot — polled live in the browser (the button shows/hides as conditions change). Same client-evaluable-only rule as `disabledWhen`.","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"type":"any","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"any"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where","typeLabel":"any[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.hiddenWhen","typeLabel":"object[]"},"variant":{"type":"string","enum":["primary","secondary"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.variant","typeLabel":"enum\u003cstring>"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["embed"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"description":"Paste the page's normal URL (e.g. a youtube.com/watch link) — on write it is resolved through the standard oEmbed provider registry (YouTube, Vimeo, Loom, Figma, …) and the provider's official embed markup is stored, so you never hand-build /embed/ URLs. A URL no provider claims is iframed as-is — it renders only if that site allows being framed (no X-Frame-Options/CSP block).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3.id","typeLabel":"string"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3.width","typeLabel":"object"},"height":{"description":"Embed height. Optional ONLY for provider embeds (YouTube/Vimeo/… size themselves by aspect ratio); a URL with NO oEmbed provider has no ratio, and omitting a pixel height leaves the iframe at the browser's built-in default — a strip ~150px tall, almost never the intended size (validate warns). For plain-iframe URLs always set `{ \"unit\": \"pixels\", \"value\": … }`.","type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3.height.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3.height.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3.height","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3.margin.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3.margin","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","question"],"properties":{"type":{"type":"string","enum":["question"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"question":{"oneOf":[{"type":"object","required":["kind","name"],"properties":{"kind":{"type":"string","enum":["nps"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.0.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.0.name","typeLabel":"string"},"cvid":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.0.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.0.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.0.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.0.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.0","typeLabel":"object"},{"type":"object","required":["kind","name","style","range"],"properties":{"kind":{"type":"string","enum":["rating"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.1.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.1.name","typeLabel":"string"},"style":{"type":"string","enum":["star","scale"],"description":"star = star rating; scale = a numeric scale. A \"scale\" question IS a rating with style:\"scale\" — there is no separate \"scale\" kind.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.1.style","typeLabel":"enum\u003cstring>"},"range":{"type":"object","properties":{"low":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.1.range.low","typeLabel":"number"},"high":{"type":"number","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.1.range.high","typeLabel":"number"}},"required":["low","high"],"description":"Numeric range, e.g. { low: 1, high: 5 }.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.1.range","typeLabel":"object"},"cvid":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.1.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.1.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.1.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.1.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.1","typeLabel":"object"},{"type":"object","required":["kind","name","multiline"],"properties":{"kind":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.2.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.2.name","typeLabel":"string"},"multiline":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.2.multiline","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.2.cvid","typeLabel":"string"},"placeholder":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.2.placeholder","typeLabel":"string"},"buttonText":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.2.buttonText","typeLabel":"string"},"required":{"description":"Require an answer before submit. ONLY `text` supports this — nps / rating / choice cannot be marked required.","type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.2.required","typeLabel":"boolean"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.2.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.2","typeLabel":"object"},{"type":"object","required":["kind","name","options","allowMultiple"],"properties":{"kind":{"type":"string","enum":["choice"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.3.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.3.name","typeLabel":"string"},"options":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].label","typeLabel":"string"},"value":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].value","typeLabel":"string"}},"required":["label","value"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX]","typeLabel":"object"},"description":"Each option has a human-facing `label` and a stored `value` — the `value` is what gets recorded/bound as the answer.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.3.options","typeLabel":"object[]"},"allowMultiple":{"type":"boolean","description":"false = single-select, true = multi-select. A multi-select answer needs a `list`-typed bound attribute.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.3.allowMultiple","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.3.cvid","typeLabel":"string"},"enableOther":{"type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.3.enableOther","typeLabel":"boolean"},"otherPlaceholder":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.3.otherPlaceholder","typeLabel":"string"},"shuffle":{"type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.3.shuffle","typeLabel":"boolean"},"buttonText":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.3.buttonText","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.3.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question.oneOf.3","typeLabel":"object"}],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.question","typeLabel":"object"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.id","typeLabel":"string"},"actions":{"description":"Actions that fire when this question is answered (on pick for nps/rating/single-select; on its Submit button for text/multi-select). Put a `goto_step` HERE to advance to the next step — without it the question records the answer but the flow does NOT advance (validate flags the next step \"not reachable\"). Do NOT add a separate `button` block just to advance: it doubles up with the question's own submit affordance.","type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4.actions","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Lays 2+ blocks out side by side in one row. Top-level `content` blocks otherwise stack vertically — each is its own full-width row — so two `button` blocks placed directly in `content` render as two stacked rows, NOT a button pair. For a modal/tooltip footer with a Skip + primary button: one `columns` block with two `{ width: { unit: \"fill\" }, blocks: [...] }` entries, one button per column.","required":["type","columns"],"properties":{"type":{"type":"string","enum":["columns"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"columns":{"type":"array","items":{"type":"object","properties":{"blocks":{"type":"array","items":{"$circularRef":"b55c69d5-0b98-4319-a584-ae0961850a0b"},"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.columns.items.[INDEX].blocks","typeLabel":"array"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels","fill"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.columns.items.[INDEX].width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.columns.items.[INDEX].width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.columns.items.[INDEX].width","typeLabel":"object"},"justify":{"description":"Horizontal alignment of this column's own block(s) within the column's width — use \"end\"/\"center\" to right-align or center a lone button (a single-column `columns` block).","type":"string","enum":["start","center","end","between","around","evenly"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.columns.items.[INDEX].justify","typeLabel":"enum\u003cstring>"},"align":{"type":"string","enum":["start","center","end","baseline"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.columns.items.[INDEX].align","typeLabel":"enum\u003cstring>"},"padding":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.right","typeLabel":"number"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding","typeLabel":"object"}},"required":["blocks"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.columns.items.[INDEX]","typeLabel":"object"},"description":"One entry per column, laid out left-to-right. Each column is a mini vertical stack of `blocks` (usually one).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.columns","typeLabel":"object[]"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5.id","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.6.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.6.id","typeLabel":"string"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.6.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX].oneOf.6","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3.content","typeLabel":"object[]"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","name"],"properties":{"type":{"type":"string","enum":["content-list"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"Visible label of this block row. Supports `{{ attribute_code | default: \"x\" }}` user-attribute interpolation.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.name","typeLabel":"string"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.id","typeLabel":"string"},"onlyShowWhen":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.onlyShowWhen","typeLabel":"object[]"},"icon":{"type":"object","properties":{"source":{"description":"Icon source. 'builtin' = a named icon from the bundled RemixIcon set (see `type`); 'upload'/'url' = a custom image via `url`; 'none' = no icon.","type":"string","enum":["none","builtin","upload","url"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.icon.source","typeLabel":"enum\u003cstring>"},"type":{"description":"Builtin icon name (when source='builtin'): a RemixIcon name in kebab `-line`/`-fill` style — e.g. `home-line`, `question-line`, `chat-line`, `settings-line`, `rocket`. NOT lucide names: `help-circle` / `sparkles` / `book-open` / `message-circle` are not in the set — validate warns on an unregistered name (the icon slot renders empty; the block itself still shows). Unsure of a name? Use source='none' rather than guess. Common names + an intent→name table are in the MCP authoring guide (icons section).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.icon.type","typeLabel":"string"},"url":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.icon.url","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.icon","typeLabel":"object"},"flowIcon":{"type":"object","properties":{"source":{"description":"Icon source. 'builtin' = a named icon from the bundled RemixIcon set (see `type`); 'upload'/'url' = a custom image via `url`; 'none' = no icon.","type":"string","enum":["none","builtin","upload","url"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.flowIcon.source","typeLabel":"enum\u003cstring>"},"type":{"description":"Builtin icon name (when source='builtin'): a RemixIcon name in kebab `-line`/`-fill` style — e.g. `home-line`, `question-line`, `chat-line`, `settings-line`, `rocket`. NOT lucide names: `help-circle` / `sparkles` / `book-open` / `message-circle` are not in the set — validate warns on an unregistered name (the icon slot renders empty; the block itself still shows). Unsure of a name? Use source='none' rather than guess. Common names + an intent→name table are in the MCP authoring guide (icons section).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.flowIcon.type","typeLabel":"string"},"url":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.flowIcon.url","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.flowIcon","typeLabel":"object"},"checklistIcon":{"type":"object","properties":{"source":{"description":"Icon source. 'builtin' = a named icon from the bundled RemixIcon set (see `type`); 'upload'/'url' = a custom image via `url`; 'none' = no icon.","type":"string","enum":["none","builtin","upload","url"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.checklistIcon.source","typeLabel":"enum\u003cstring>"},"type":{"description":"Builtin icon name (when source='builtin'): a RemixIcon name in kebab `-line`/`-fill` style — e.g. `home-line`, `question-line`, `chat-line`, `settings-line`, `rocket`. NOT lucide names: `help-circle` / `sparkles` / `book-open` / `message-circle` are not in the set — validate warns on an unregistered name (the icon slot renders empty; the block itself still shows). Unsure of a name? Use source='none' rather than guess. Common names + an intent→name table are in the MCP authoring guide (icons section).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.checklistIcon.type","typeLabel":"string"},"url":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.checklistIcon.url","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.checklistIcon","typeLabel":"object"},"showSearchField":{"type":"boolean","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.showSearchField","typeLabel":"boolean"},"items":{"default":[],"type":"array","items":{"type":"object","properties":{"content":{"type":"string","description":"Id of the flow/checklist this entry starts on click.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].content","typeLabel":"string"},"contentType":{"type":"string","enum":["flow","checklist"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].contentType","typeLabel":"enum\u003cstring>"},"label":{"description":"Display name for this list entry; omitted or empty falls back to the referenced content's admin name. `items` is a full-list replacement — when rewriting it, echo the read-back `label` or it is cleared.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].label","typeLabel":"string"},"icon":{"type":"object","properties":{"source":{"description":"Icon source. 'inherit' (default) falls back to the block's flowIcon/checklistIcon; 'builtin' = a named RemixIcon (see `type`); 'upload'/'url' = a custom image; 'none' = no icon.","type":"string","enum":["none","builtin","upload","url","inherit"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].icon.source","typeLabel":"enum\u003cstring>"},"type":{"description":"Builtin icon name (when source='builtin'): a RemixIcon name in kebab `-line`/`-fill` style — e.g. `home-line`, `question-line`, `chat-line`, `settings-line`, `rocket`. NOT lucide names: `help-circle` / `sparkles` / `book-open` / `message-circle` are not in the set — validate warns on an unregistered name (the icon slot renders empty; the block itself still shows). Unsure of a name? Use source='none' rather than guess. Common names + an intent→name table are in the MCP authoring guide (icons section).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].icon.type","typeLabel":"string"},"url":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].icon.url","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].icon","typeLabel":"object"},"navigateUrl":{"description":"URL also opened when the entry is clicked, AFTER the referenced content starts — e.g. take the user to the page where the flow begins. Supports `{{ attribute_code | default: \"x\" }}` interpolation.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].navigateUrl","typeLabel":"string"},"navigateOpenType":{"description":"Open `navigateUrl` in the same tab (default) or a new one.","type":"string","enum":["same","new"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].navigateOpenType","typeLabel":"enum\u003cstring>"},"onlyShowWhen":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX].onlyShowWhen","typeLabel":"object[]"}},"required":["content","contentType"],"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4.\\items","typeLabel":"object[]"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","name","provider"],"properties":{"type":{"type":"string","enum":["live-chat"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"Visible label of this block row. Supports `{{ attribute_code | default: \"x\" }}` user-attribute interpolation.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.name","typeLabel":"string"},"provider":{"type":"string","enum":["crisp","custom","freshchat","help-scout","hubspot","intercom","zendesk-classic","zendesk-messenger"],"description":"Which chat widget the click opens. The SDK only INVOKES the provider — the host page must already have that provider's script installed, or clicking opens nothing (the SDK logs a warning naming the provider, but its console output is off by default — visible only with the debug flag `localStorage.debug = 'usertour-widget:*'`). Not checkable at write time (the server cannot see the host page), so confirm which provider the host actually runs.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.provider","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.id","typeLabel":"string"},"onlyShowWhen":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.onlyShowWhen","typeLabel":"object[]"},"icon":{"type":"object","properties":{"source":{"description":"Icon source. 'builtin' = a named icon from the bundled RemixIcon set (see `type`); 'upload'/'url' = a custom image via `url`; 'none' = no icon.","type":"string","enum":["none","builtin","upload","url"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.icon.source","typeLabel":"enum\u003cstring>"},"type":{"description":"Builtin icon name (when source='builtin'): a RemixIcon name in kebab `-line`/`-fill` style — e.g. `home-line`, `question-line`, `chat-line`, `settings-line`, `rocket`. NOT lucide names: `help-circle` / `sparkles` / `book-open` / `message-circle` are not in the set — validate warns on an unregistered name (the icon slot renders empty; the block itself still shows). Unsure of a name? Use source='none' rather than guess. Common names + an intent→name table are in the MCP authoring guide (icons section).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.icon.type","typeLabel":"string"},"url":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.icon.url","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.icon","typeLabel":"object"},"customCode":{"description":"Only meaningful — and only RETURNED — when provider is 'custom': the script run on CLICK (not page load) via new Function; hosts can disable it with usertour.disableEvalJs(). Echo-only via the API — echo the stored code back unchanged (or omit the field) to keep it, empty string clears it; writing new or edited code is rejected (same security policy as run_javascript). Scripts can only be authored in the Usertour app. For other providers the field never appears in reads; any leftover script from a past provider switch stays stored and resurfaces if the provider returns to custom.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5.customCode","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","name"],"properties":{"type":{"type":"string","enum":["announcement"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"Visible label of this block row. Supports `{{ attribute_code | default: \"x\" }}` user-attribute interpolation.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.name","typeLabel":"string"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.id","typeLabel":"string"},"onlyShowWhen":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.onlyShowWhen","typeLabel":"object[]"},"icon":{"type":"object","properties":{"source":{"description":"Icon source. 'builtin' = a named icon from the bundled RemixIcon set (see `type`); 'upload'/'url' = a custom image via `url`; 'none' = no icon.","type":"string","enum":["none","builtin","upload","url"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.icon.source","typeLabel":"enum\u003cstring>"},"type":{"description":"Builtin icon name (when source='builtin'): a RemixIcon name in kebab `-line`/`-fill` style — e.g. `home-line`, `question-line`, `chat-line`, `settings-line`, `rocket`. NOT lucide names: `help-circle` / `sparkles` / `book-open` / `message-circle` are not in the set — validate warns on an unregistered name (the icon slot renders empty; the block itself still shows). Unsure of a name? Use source='none' rather than guess. Common names + an intent→name table are in the MCP authoring guide (icons section).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.icon.type","typeLabel":"string"},"url":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.icon.url","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6.icon","typeLabel":"object"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.id","typeLabel":"string"},"onlyShowWhen":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.onlyShowWhen","typeLabel":"object[]"},"note":{"type":"string","uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7.note","typeLabel":"string"}},"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX].oneOf.7","typeLabel":"object"}],"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs.items.[INDEX].blocks","typeLabel":"object[]"}},"required":["name"],"additionalProperties":false,"uniqueKey":"data.oneOf.5.tabs.items.[INDEX]","typeLabel":"object"},"uniqueKey":"data.oneOf.5.tabs","typeLabel":"object[]"}},"additionalProperties":{"oneOf":[{}],"uniqueKey":"data.oneOf.5.additionalProperties","typeLabel":"any"},"uniqueKey":"data.oneOf.5","typeLabel":"object"}],"uniqueKey":"data","typeLabel":"object"},"scheduledAt":{"oneOf":[{"type":"string","description":"Announcement versions only: the \"announcement time\" — the feed hides the announcement until this instant passes, and orders the feed by it (newest first). ISO date or datetime WITH timezone. `null` = clear (publish stamps the publish time instead). A future value defers visibility; the value carries across version forks.","uniqueKey":"scheduledAt.oneOf.0","typeLabel":"string"},{"type":"null","description":"Announcement versions only: the \"announcement time\" — the feed hides the announcement until this instant passes, and orders the feed by it (newest first). ISO date or datetime WITH timezone. `null` = clear (publish stamps the publish time instead). A future value defers visibility; the value carries across version forks.","uniqueKey":"scheduledAt.oneOf.1","typeLabel":"null"}],"uniqueKey":"scheduledAt","typeLabel":"string | null"}},"additionalProperties":false,"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"steps":[{"name":"\u003cstring>","id":"\u003cstring>","cvid":"\u003cstring>","key":"\u003cstring>","sequence":123,"themeId":"\u003cstring>","placement":{"sideOffset":123,"alignOffset":123,"backdrop":true,"blockTarget":true},"width":123,"skippable":true,"explicitCompletionStep":true,"content":[{"type":"text","markdown":"\u003cstring>","object":"block","id":"\u003cstring>"}],"triggers":[{"do":[{"type":"goto_step","step":"\u003cstring>"}],"when":[{"type":"group","conditions":"\u003carray>"}],"waitSeconds":123}],"onClick":[{"type":"goto_step","step":"\u003cstring>"}]}],"startRules":{"when":[{"type":"group","conditions":"\u003carray>"}],"waitSeconds":123,"startIfNotComplete":true},"hideRules":{"when":[{"type":"group","conditions":"\u003carray>"}]},"themeId":"\u003cstring>","data":{"buttonText":"\u003cstring>","preventDismiss":true,"autoDismiss":true,"content":[{"type":"text","markdown":"\u003cstring>","object":"block","id":"\u003cstring>"}],"items":[{"name":"\u003cstring>","id":"\u003cstring>","description":"\u003cstring>","completeWhen":[],"clickActions":[],"onlyShowWhen":[{"type":"group","conditions":"\u003carray>"}]}]},"scheduledAt":"\u003cstring>"}}},"exampleType":"generated"}},"exampleType":"generated"},"responses":{"3c423bb5-6430-407e-836f-bfdeda54f538":{"description":"Updated content version","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","isRequired":true,"uniqueKey":"id","typeLabel":"string"},"object":{"type":"string","enum":["contentVersion"],"isRequired":true,"uniqueKey":"object","typeLabel":"enum\u003cstring>"},"number":{"type":"number","isRequired":true,"uniqueKey":"number","typeLabel":"number"},"firstPublishedAt":{"oneOf":[{"type":"string","description":"When this version FIRST went live (ISO). Non-null means the version is frozen: it can never be edited again — not even after unpublishing — edit by forking (by forking). null means it never went live — OR its first publish predates this stamp (older versions were never backfilled), so on old data null is not proof it never shipped. This is history, not live state: for \"is it live NOW, where\" read the content's `environments[]`; for who published what when, the publish history (available through the MCP).","isRequired":true,"uniqueKey":"firstPublishedAt.oneOf.0","typeLabel":"string"},{"type":"null","description":"When this version FIRST went live (ISO). Non-null means the version is frozen: it can never be edited again — not even after unpublishing — edit by forking (by forking). null means it never went live — OR its first publish predates this stamp (older versions were never backfilled), so on old data null is not proof it never shipped. This is history, not live state: for \"is it live NOW, where\" read the content's `environments[]`; for who published what when, the publish history (available through the MCP).","isRequired":true,"uniqueKey":"firstPublishedAt.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"firstPublishedAt","typeLabel":"string | null"},"themeId":{"oneOf":[{"type":"string","isRequired":true,"uniqueKey":"themeId.oneOf.0","typeLabel":"string"},{"type":"null","isRequired":true,"uniqueKey":"themeId.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"themeId","typeLabel":"string | null"},"questions":{"oneOf":[{"type":"array","items":{"type":"object","properties":{"object":{"type":"string","enum":["question"],"isRequired":true,"uniqueKey":"questions.oneOf.0.items.[INDEX].object","typeLabel":"enum\u003cstring>"},"cvid":{"type":"string","isRequired":true,"uniqueKey":"questions.oneOf.0.items.[INDEX].cvid","typeLabel":"string"},"name":{"type":"string","isRequired":true,"uniqueKey":"questions.oneOf.0.items.[INDEX].name","typeLabel":"string"},"type":{"type":"string","enum":["nps","star-rating","scale","single-line-text","multi-line-text","multiple-choice"],"isRequired":true,"uniqueKey":"questions.oneOf.0.items.[INDEX].type","typeLabel":"enum\u003cstring>"}},"required":["object","cvid","name","type"],"uniqueKey":"questions.oneOf.0.items.[INDEX]","typeLabel":"object"},"isRequired":true,"uniqueKey":"questions.oneOf.0","typeLabel":"object[]"},{"type":"null","items":{"type":"object","properties":{"object":{"type":"string","enum":["question"]},"cvid":{"type":"string"},"name":{"type":"string"},"type":{"type":"string","enum":["nps","star-rating","scale","single-line-text","multi-line-text","multiple-choice"]}},"required":["object","cvid","name","type"]},"isRequired":true,"uniqueKey":"questions.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"questions","typeLabel":"object[] | null"},"updatedAt":{"type":"string","format":"date-time","isRequired":true,"uniqueKey":"updatedAt","typeLabel":"string\u003cdate-time>"},"createdAt":{"type":"string","format":"date-time","isRequired":true,"uniqueKey":"createdAt","typeLabel":"string\u003cdate-time>"},"steps":{"type":"array","items":{"type":"object","properties":{"object":{"type":"string","enum":["step"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].id","typeLabel":"string"},"cvid":{"oneOf":[{"type":"string","description":"Stable step handle that SURVIVES forking (unlike `id`, which is regenerated) — echo it on a write to update this step in place. Prefer it over `id` for edits that must outlive a new version.","isRequired":true,"uniqueKey":"steps.items.[INDEX].cvid.oneOf.0","typeLabel":"string"},{"type":"null","description":"Stable step handle that SURVIVES forking (unlike `id`, which is regenerated) — echo it on a write to update this step in place. Prefer it over `id` for edits that must outlive a new version.","isRequired":true,"uniqueKey":"steps.items.[INDEX].cvid.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"steps.items.[INDEX].cvid","typeLabel":"string | null"},"name":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].name","typeLabel":"string"},"type":{"type":"string","enum":["tooltip","modal","hidden","bubble"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].type","typeLabel":"enum\u003cstring>"},"sequence":{"type":"number","description":"0-based display order. On write an explicit `sequence` wins; steps without one fall back to their array index.","isRequired":true,"uniqueKey":"steps.items.[INDEX].sequence","typeLabel":"number"},"themeId":{"oneOf":[{"type":"string","description":"Per-step theme override; null = this step inherits the flow version's theme.","isRequired":true,"uniqueKey":"steps.items.[INDEX].themeId.oneOf.0","typeLabel":"string"},{"type":"null","description":"Per-step theme override; null = this step inherits the flow version's theme.","isRequired":true,"uniqueKey":"steps.items.[INDEX].themeId.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"steps.items.[INDEX].themeId","typeLabel":"string | null"},"content":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","markdown"],"properties":{"type":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"markdown":{"type":"string","description":"A small markdown subset: paragraphs, `# `/`## ` headings (h1/h2 only — no h3+), `-`/`*` and `1.` lists, ``` code fences; inline `**bold**`, `*italic*`, `[text](url)`, and `{{ attribute_code | default: \"x\" }}` for user attributes. Anything outside this subset is SILENTLY normalized, not rejected: h3+ → h2; blockquotes flatten to paragraphs; tables, horizontal rules, strikethrough, inline images/code, and liquid filters other than `default` are dropped. Unsupported syntax won't round-trip — don't rely on it.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.0.markdown","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.0.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.0.id","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["image"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.id","typeLabel":"string"},"alt":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.alt","typeLabel":"string"},"link":{"type":"object","properties":{"url":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.link.url","typeLabel":"string"},"newTab":{"type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.link.newTab","typeLabel":"boolean"}},"required":["url"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.link","typeLabel":"object"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.width","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.margin.right","typeLabel":"number"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1.margin","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","text"],"properties":{"type":{"type":"string","enum":["button"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"text":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.text","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.id","typeLabel":"string"},"actions":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.actions","typeLabel":"object[]"},"disabledWhen":{"description":"REACTIVE slot — polled live in the browser (the button disables the moment the conditions match). Client-evaluable condition types only: attribute / current_url / element / text_input / text_filled / time_window; event / segment / content_state are rejected (E1017).","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"35ab8279-f569-41c3-8191-173d22799444"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"4abbcf19-e35d-4250-a5ae-cddc55893958"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.disabledWhen","typeLabel":"object[]"},"hiddenWhen":{"description":"REACTIVE slot — polled live in the browser (the button shows/hides as conditions change). Same client-evaluable-only rule as `disabledWhen`.","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"35ab8279-f569-41c3-8191-173d22799444"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"4abbcf19-e35d-4250-a5ae-cddc55893958"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.hiddenWhen","typeLabel":"object[]"},"variant":{"type":"string","enum":["primary","secondary"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.variant","typeLabel":"enum\u003cstring>"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.margin.right","typeLabel":"number"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2.margin","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["embed"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","minLength":1,"description":"Paste the page's normal URL (e.g. a youtube.com/watch link) — on write it is resolved through the standard oEmbed provider registry (YouTube, Vimeo, Loom, Figma, …) and the provider's official embed markup is stored, so you never hand-build /embed/ URLs. A URL no provider claims is iframed as-is — it renders only if that site allows being framed (no X-Frame-Options/CSP block).","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.url","typeLabel":"string"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.id","typeLabel":"string"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.width","typeLabel":"object"},"height":{"description":"Embed height. Optional ONLY for provider embeds (YouTube/Vimeo/… size themselves by aspect ratio); a URL with NO oEmbed provider has no ratio, and omitting a pixel height leaves the iframe at the browser's built-in default — a strip ~150px tall, almost never the intended size (validate warns). For plain-iframe URLs always set `{ \"unit\": \"pixels\", \"value\": … }`.","type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.height.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.height.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.height","typeLabel":"object"},"margin":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.margin.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.margin.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.margin.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.margin.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.margin.right","typeLabel":"number"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3.margin","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","question"],"properties":{"type":{"type":"string","enum":["question"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"question":{"oneOf":[{"type":"object","required":["kind","name"],"properties":{"kind":{"type":"string","enum":["nps"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.0.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.0.name","typeLabel":"string"},"cvid":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.0.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.0.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.0.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.0.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.0","typeLabel":"object"},{"type":"object","required":["kind","name","style","range"],"properties":{"kind":{"type":"string","enum":["rating"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.name","typeLabel":"string"},"style":{"type":"string","enum":["star","scale"],"description":"star = star rating; scale = a numeric scale. A \"scale\" question IS a rating with style:\"scale\" — there is no separate \"scale\" kind.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.style","typeLabel":"enum\u003cstring>"},"range":{"type":"object","properties":{"low":{"type":"number","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.range.low","typeLabel":"number"},"high":{"type":"number","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.range.high","typeLabel":"number"}},"required":["low","high"],"description":"Numeric range, e.g. { low: 1, high: 5 }.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.range","typeLabel":"object"},"cvid":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.cvid","typeLabel":"string"},"lowLabel":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.lowLabel","typeLabel":"string"},"highLabel":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.highLabel","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.1","typeLabel":"object"},{"type":"object","required":["kind","name","multiline"],"properties":{"kind":{"type":"string","enum":["text"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2.name","typeLabel":"string"},"multiline":{"type":"boolean","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2.multiline","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2.cvid","typeLabel":"string"},"placeholder":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2.placeholder","typeLabel":"string"},"buttonText":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2.buttonText","typeLabel":"string"},"required":{"description":"Require an answer before submit. ONLY `text` supports this — nps / rating / choice cannot be marked required.","type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2.required","typeLabel":"boolean"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.2","typeLabel":"object"},{"type":"object","required":["kind","name","options","allowMultiple"],"properties":{"kind":{"type":"string","enum":["choice"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.kind","typeLabel":"enum\u003cstring>"},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.name","typeLabel":"string"},"options":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].label","typeLabel":"string"},"value":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX].value","typeLabel":"string"}},"required":["label","value"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.options.items.[INDEX]","typeLabel":"object"},"description":"Each option has a human-facing `label` and a stored `value` — the `value` is what gets recorded/bound as the answer.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.options","typeLabel":"object[]"},"allowMultiple":{"type":"boolean","description":"false = single-select, true = multi-select. A multi-select answer needs a `list`-typed bound attribute.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.allowMultiple","typeLabel":"boolean"},"cvid":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.cvid","typeLabel":"string"},"enableOther":{"type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.enableOther","typeLabel":"boolean"},"otherPlaceholder":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.otherPlaceholder","typeLabel":"string"},"shuffle":{"type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.shuffle","typeLabel":"boolean"},"buttonText":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.buttonText","typeLabel":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3.bindAttribute","typeLabel":"string"}},"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question.oneOf.3","typeLabel":"object"}],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.question","typeLabel":"object"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.id","typeLabel":"string"},"actions":{"description":"Actions that fire when this question is answered (on pick for nps/rating/single-select; on its Submit button for text/multi-select). Put a `goto_step` HERE to advance to the next step — without it the question records the answer but the flow does NOT advance (validate flags the next step \"not reachable\"). Do NOT add a separate `button` block just to advance: it doubles up with the question's own submit affordance.","type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4.actions","typeLabel":"object[]"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Lays 2+ blocks out side by side in one row. Top-level `content` blocks otherwise stack vertically — each is its own full-width row — so two `button` blocks placed directly in `content` render as two stacked rows, NOT a button pair. For a modal/tooltip footer with a Skip + primary button: one `columns` block with two `{ width: { unit: \"fill\" }, blocks: [...] }` entries, one button per column.","required":["type","columns"],"properties":{"type":{"type":"string","enum":["columns"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"columns":{"type":"array","items":{"type":"object","properties":{"blocks":{"type":"array","items":{"$circularRef":"05a3f782-7797-4a54-937f-60ab56852e98"},"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].blocks","typeLabel":"array"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels","fill"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].width.unit","typeLabel":"enum\u003cstring>"},"value":{"type":"number","minimum":0,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].width.value","typeLabel":"number"}},"required":["unit"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].width","typeLabel":"object"},"justify":{"description":"Horizontal alignment of this column's own block(s) within the column's width — use \"end\"/\"center\" to right-align or center a lone button (a single-column `columns` block).","type":"string","enum":["start","center","end","between","around","evenly"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].justify","typeLabel":"enum\u003cstring>"},"align":{"type":"string","enum":["start","center","end","baseline"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].align","typeLabel":"enum\u003cstring>"},"padding":{"type":"object","properties":{"enabled":{"type":"boolean","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.enabled","typeLabel":"boolean"},"top":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.top","typeLabel":"number"},"bottom":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.bottom","typeLabel":"number"},"left":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.left","typeLabel":"number"},"right":{"type":"number","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding.right","typeLabel":"number"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX].padding","typeLabel":"object"}},"required":["blocks"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns.items.[INDEX]","typeLabel":"object"},"description":"One entry per column, laid out left-to-right. Each column is a mini vertical stack of `blocks` (usually one).","isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.columns","typeLabel":"object[]"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5.id","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"object":{"default":"block","type":"string","enum":["block"],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.6.object","typeLabel":"enum\u003cstring>"},"id":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.6.id","typeLabel":"string"},"note":{"type":"string","uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.6.note","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].content.items.[INDEX].oneOf.6","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].content.items.[INDEX]","typeLabel":"object"},"isRequired":true,"uniqueKey":"steps.items.[INDEX].content","typeLabel":"object[]"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].target","typeLabel":"object"},"placement":{"oneOf":[{"type":"object","description":"Two placement shapes, by step kind: a TOOLTIP (anchored to a `target`) uses `{ side, align, sideOffset?, alignOffset?, alignType? }` positioned relative to the element; a MODAL uses `{ position, offsetX?, offsetY? }` on a 9-cell viewport grid (e.g. `\"center\"`). Both may set `backdrop`; `blockTarget` is TOOLTIP-ONLY (a modal already covers the page) and additionally requires `backdrop: true` — it makes the backdrop swallow clicks on the highlighted element. A BUBBLE step is positioned by its THEME's bubble placement, so the only key it accepts here is `{ backdrop }` (positional keys are rejected — move the bubble by changing the theme); a HIDDEN step renders no UI and rejects `placement` entirely.","properties":{"side":{"description":"Which side of the target the tooltip sits on. OMIT side+align to auto-position (the tooltip picks a spot and flips to avoid the viewport edge — best when you can't see the element). Setting side (or align) pins that direction: the tooltip renders exactly there and no longer auto-flips, so only pin when the element is not near a screen edge. When READING a step back, check `alignType` first: under `auto` the side/align you see are just the auto starting position, not a pin.","type":"string","enum":["top","right","bottom","left"],"uniqueKey":"steps.items.[INDEX].placement.oneOf.0.side","typeLabel":"enum\u003cstring>"},"align":{"description":"Alignment along the side. See `side`.","type":"string","enum":["start","center","end"],"uniqueKey":"steps.items.[INDEX].placement.oneOf.0.align","typeLabel":"enum\u003cstring>"},"sideOffset":{"description":"Pixels between the target and the tooltip, along `side`.","type":"number","uniqueKey":"steps.items.[INDEX].placement.oneOf.0.sideOffset","typeLabel":"number"},"alignOffset":{"description":"Pixel shift along the alignment axis. Only applies when `align` is `start` or `end` — at `center` alignment the runtime ignores it silently.","type":"number","uniqueKey":"steps.items.[INDEX].placement.oneOf.0.alignOffset","typeLabel":"number"},"alignType":{"description":"Position mode. `auto` auto-positions and flips to avoid the viewport edge (ignoring side/align); `fixed` pins to side/align without flipping. Usually omit it: providing side/align implies `fixed`, and omitting them implies `auto`. Set it only to override that (e.g. `auto` while still passing a side/align you want ignored). Read-backs always carry concrete side/align even in `auto` mode (the auto starting position, bottom/center) — `alignType` is what governs, and `auto` still flips at runtime.","type":"string","enum":["auto","fixed"],"uniqueKey":"steps.items.[INDEX].placement.oneOf.0.alignType","typeLabel":"enum\u003cstring>"},"backdrop":{"description":"Dim the rest of the page while this tooltip is up.","type":"boolean","uniqueKey":"steps.items.[INDEX].placement.oneOf.0.backdrop","typeLabel":"boolean"},"blockTarget":{"description":"Make the backdrop swallow clicks on the highlighted element, so the user must use the tooltip. REQUIRES `backdrop: true` — on its own it does nothing (the renderer only consults it inside the backdrop branch).","type":"boolean","uniqueKey":"steps.items.[INDEX].placement.oneOf.0.blockTarget","typeLabel":"boolean"}},"additionalProperties":false,"uniqueKey":"steps.items.[INDEX].placement.oneOf.0","typeLabel":"object"},{"type":"object","description":"Two placement shapes, by step kind: a TOOLTIP (anchored to a `target`) uses `{ side, align, sideOffset?, alignOffset?, alignType? }` positioned relative to the element; a MODAL uses `{ position, offsetX?, offsetY? }` on a 9-cell viewport grid (e.g. `\"center\"`). Both may set `backdrop`; `blockTarget` is TOOLTIP-ONLY (a modal already covers the page) and additionally requires `backdrop: true` — it makes the backdrop swallow clicks on the highlighted element. A BUBBLE step is positioned by its THEME's bubble placement, so the only key it accepts here is `{ backdrop }` (positional keys are rejected — move the bubble by changing the theme); a HIDDEN step renders no UI and rejects `placement` entirely.","required":["position"],"properties":{"position":{"type":"string","enum":["leftTop","centerTop","rightTop","leftCenter","center","rightCenter","leftBottom","centerBottom","rightBottom"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].placement.oneOf.1.position","typeLabel":"enum\u003cstring>"},"offsetX":{"description":"Pixel shift from the grid cell. Applies in every cell EXCEPT `position: \"center\"`, where both offsets are ignored silently.","type":"number","uniqueKey":"steps.items.[INDEX].placement.oneOf.1.offsetX","typeLabel":"number"},"offsetY":{"description":"See `offsetX`.","type":"number","uniqueKey":"steps.items.[INDEX].placement.oneOf.1.offsetY","typeLabel":"number"},"backdrop":{"type":"boolean","uniqueKey":"steps.items.[INDEX].placement.oneOf.1.backdrop","typeLabel":"boolean"}},"additionalProperties":false,"uniqueKey":"steps.items.[INDEX].placement.oneOf.1","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].placement","typeLabel":"object"},"width":{"description":"Per-step width override in pixels (border-box outer width) — absent = the theme's surface width.","type":"number","uniqueKey":"steps.items.[INDEX].width","typeLabel":"number"},"skippable":{"type":"boolean","uniqueKey":"steps.items.[INDEX].skippable","typeLabel":"boolean"},"explicitCompletionStep":{"description":"Marks this step as the flow's completion point: reaching it counts the flow as COMPLETED (progress hits 100 and the completion event fires there), and later steps no longer report progress. With no step marked, only reaching the LAST step completes the flow — which is what a checklist task waiting on \"this flow completed\" depends on.","type":"boolean","uniqueKey":"steps.items.[INDEX].explicitCompletionStep","typeLabel":"boolean"},"triggers":{"type":"array","items":{"type":"object","properties":{"do":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do.items.[INDEX]","typeLabel":"object"},"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].do","typeLabel":"object[]"},"when":{"description":"REACTIVE slot — polled live in the browser while the step is on screen, so it accepts only client-evaluable condition types: attribute / current_url / element / text_input / text_filled / time_window. Event / segment / content_state conditions are server-evaluated and rejected here (E1017). A trigger with NO `when` never fires (an empty set is \"not matched\", not \"always\").","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"35ab8279-f569-41c3-8191-173d22799444"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"4abbcf19-e35d-4250-a5ae-cddc55893958"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].when","typeLabel":"object[]"},"waitSeconds":{"description":"Delay in SECONDS between the `when` conditions matching and the `do` actions firing. The timer arms the first time `when` matches and the actions fire after the wait EVEN IF the conditions have since stopped matching (the match is latched, not re-checked). Capped at 300 seconds by the runtime (a larger value is clamped).","type":"number","uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX].waitSeconds","typeLabel":"number"}},"required":["do"],"uniqueKey":"steps.items.[INDEX].triggers.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].triggers","typeLabel":"object[]"},"onClick":{"description":"Actions that run when the user clicks the step's TARGET ELEMENT on the page (click-to-advance) — distinct from a `button` block, whose actions fire on a button rendered inside the step. `tooltip` steps only: the other kinds have no target element to click, and the write rejects `onClick` on them.","type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.0.step","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.1.content","typeLabel":"string"},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string","uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.1.step","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.2.url","typeLabel":"string"},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean","uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.2.newTab","typeLabel":"boolean"}},"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"}},"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"script":{"type":"string","isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.4.script","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.5.note","typeLabel":"string"}},"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX].oneOf.5","typeLabel":"object"}],"uniqueKey":"steps.items.[INDEX].onClick.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps.items.[INDEX].onClick","typeLabel":"object[]"},"advanced":{"type":"object","properties":{"hasUnsupported":{"type":"boolean","description":"true = this step carries stored configuration this API cannot express (e.g. an auto-recorded element fingerprint). Read-only signal: echoing the step back preserves that configuration untouched; it is not editable here.","isRequired":true,"uniqueKey":"steps.items.[INDEX].advanced.hasUnsupported","typeLabel":"boolean"}},"required":["hasUnsupported"],"uniqueKey":"steps.items.[INDEX].advanced","typeLabel":"object"}},"required":["object","id","cvid","name","type","sequence","themeId","content"],"uniqueKey":"steps.items.[INDEX]","typeLabel":"object"},"uniqueKey":"steps","typeLabel":"object[]"},"startRules":{"type":"object","properties":{"when":{"description":"Start conditions (full replacement when present). Omit to keep the existing conditions and change only the settings.","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"35ab8279-f569-41c3-8191-173d22799444"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"startRules.when.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"startRules.when.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"startRules.when.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"startRules.when.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"startRules.when.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"startRules.when.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"startRules.when.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"startRules.when.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"startRules.when.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"startRules.when.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"startRules.when.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"startRules.when.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"startRules.when.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"startRules.when.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"startRules.when.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"startRules.when.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"startRules.when.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"startRules.when.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"startRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"startRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"startRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"4abbcf19-e35d-4250-a5ae-cddc55893958"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"startRules.when.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"startRules.when.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"startRules.when.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"startRules.when.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"startRules.when.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"startRules.when.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"startRules.when.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"startRules.when.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"startRules.when.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"startRules.when.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"startRules.when.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"startRules.when.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"startRules.when.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"startRules.when.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"startRules.when.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"startRules.when.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"startRules.when.items.[INDEX]","typeLabel":"object"},"uniqueKey":"startRules.when","typeLabel":"object[]"},"frequency":{"description":"How often the content may auto-start (flow/checklist only). Left unset on a write, the builder default (`once`) is seeded and STORED — read-backs show the effective mode explicitly. A version stored with NO frequency at all (reachable only through legacy/builder-external writes, never through this API) runs with NO limit: it starts again every time its rules match once the prior session ends — validate_content_version warns on that state.","type":"object","properties":{"mode":{"type":"string","enum":["once","multiple","unlimited"],"description":"once = show a single time; multiple = up to N times per window; unlimited = every time the conditions match (subject to `every`).","isRequired":true,"uniqueKey":"startRules.frequency.mode","typeLabel":"enum\u003cstring>"},"every":{"description":"Re-show window. Used by `multiple` (with `times`) and `unlimited`; ignored for `once`. If omitted for those modes a default window is applied. Manual and programmatic starts also count toward the `multiple` limit.","type":"object","properties":{"duration":{"type":"number","isRequired":true,"uniqueKey":"startRules.frequency.every.duration","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"isRequired":true,"uniqueKey":"startRules.frequency.every.unit","typeLabel":"enum\u003cstring>"},"times":{"type":"number","uniqueKey":"startRules.frequency.every.times","typeLabel":"number"}},"required":["duration","unit"],"uniqueKey":"startRules.frequency.every","typeLabel":"object"},"atLeast":{"description":"Only auto-start if no OTHER content of the SAME type has been shown within this window — and since only flows accept this knob, in practice: no other FLOW. A banner / checklist / launcher showing does NOT block it. Avoids stacking several flows on a user at once.","type":"object","properties":{"duration":{"type":"number","isRequired":true,"uniqueKey":"startRules.frequency.atLeast.duration","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"isRequired":true,"uniqueKey":"startRules.frequency.atLeast.unit","typeLabel":"enum\u003cstring>"}},"required":["duration","unit"],"uniqueKey":"startRules.frequency.atLeast","typeLabel":"object"}},"required":["mode"],"uniqueKey":"startRules.frequency","typeLabel":"object"},"priority":{"description":"Tie-breaker when a user matches the start conditions for more than one piece of content at the same time — the higher priority starts first. Content with no priority set ranks as `medium`.","type":"string","enum":["highest","high","medium","low","lowest"],"uniqueKey":"startRules.priority","typeLabel":"enum\u003cstring>"},"waitSeconds":{"description":"Delay in SECONDS between the start conditions matching and the content becoming ELIGIBLE to start. The countdown itself survives the conditions un-matching mid-wait, but unlike a trigger wait (which fires its actions regardless), an elapsed start-rule wait guarantees nothing: the conditions are RE-CHECKED at show time, and the content starts at the next moment they match again. One exception: when a session of the same content type starts or ends mid-wait, in-flight timers are cancelled and re-armed — a still-matching version then restarts its wait from zero. Capped at 300 seconds by the runtime (a larger value is clamped).","type":"number","uniqueKey":"startRules.waitSeconds","typeLabel":"number"},"startIfNotComplete":{"description":"When true, this content won't auto-start for users who have already completed it.","type":"boolean","uniqueKey":"startRules.startIfNotComplete","typeLabel":"boolean"}},"uniqueKey":"startRules","typeLabel":"object"},"hideRules":{"type":"object","properties":{"when":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.0.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"35ab8279-f569-41c3-8191-173d22799444"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.0.conditions","typeLabel":"array"}},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.1.scope","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.1.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.1.op","typeLabel":"enum\u003cstring>"},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string","uniqueKey":"hideRules.when.items.[INDEX].oneOf.1.value","typeLabel":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string","uniqueKey":"hideRules.when.items.[INDEX].oneOf.1.value2","typeLabel":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string","uniqueKey":"hideRules.when.items.[INDEX].oneOf.1.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.1.values","typeLabel":"string[]"}},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.1","typeLabel":"object"},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.2.type","typeLabel":"enum\u003cstring>"},"segment":{"type":"string","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.2.segment","typeLabel":"string"},"in":{"type":"boolean","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.2.in","typeLabel":"boolean"}},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.2","typeLabel":"object"},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.3.type","typeLabel":"enum\u003cstring>"},"includes":{"type":"array","items":{"type":"string","uniqueKey":"hideRules.when.items.[INDEX].oneOf.3.includes.items.[INDEX]","typeLabel":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.3.includes","typeLabel":"string[]"},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string","uniqueKey":"hideRules.when.items.[INDEX].oneOf.3.excludes.items.[INDEX]","typeLabel":"string"},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.3.excludes","typeLabel":"string[]"}},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.3","typeLabel":"object"},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.4.type","typeLabel":"enum\u003cstring>"},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.4.state","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.4.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"hideRules.when.items.[INDEX].oneOf.4.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.4.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"hideRules.when.items.[INDEX].oneOf.4.target","typeLabel":"object"}},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.4","typeLabel":"object"},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.5.type","typeLabel":"enum\u003cstring>"},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.5.content","typeLabel":"string"},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.5.state","typeLabel":"enum\u003cstring>"}},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.5","typeLabel":"object"},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.type","typeLabel":"enum\u003cstring>"},"event":{"type":"string","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.event","typeLabel":"string"},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.count.op","typeLabel":"enum\u003cstring>"},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.count.n","typeLabel":"integer"},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.count.n2","typeLabel":"integer"}},"required":["op","n"],"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.count","typeLabel":"object"},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.within.op","typeLabel":"enum\u003cstring>"},"value":{"type":"number","uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.within.value","typeLabel":"number"},"value2":{"type":"number","uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.within.value2","typeLabel":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"],"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.within.unit","typeLabel":"enum\u003cstring>"}},"required":["op"],"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.within","typeLabel":"object"},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"],"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.scope","typeLabel":"enum\u003cstring>"},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.type","typeLabel":"enum\u003cstring>"},"attribute":{"type":"string","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.attribute","typeLabel":"string"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.op","typeLabel":"enum\u003cstring>"},"value":{"type":"string","uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value","typeLabel":"string"},"value2":{"type":"string","uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.value2","typeLabel":"string"},"values":{"type":"array","items":{"type":"string","uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values.items.[INDEX]","typeLabel":"string"},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0.values","typeLabel":"string[]"}},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.0","typeLabel":"object"},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.type","typeLabel":"enum\u003cstring>"},"match":{"type":"string","enum":["all","any"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.match","typeLabel":"enum\u003cstring>"},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"4abbcf19-e35d-4250-a5ae-cddc55893958"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1.conditions","typeLabel":"array"}},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.where.items.[INDEX].oneOf.1","typeLabel":"object"}],"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.where.items.[INDEX]","typeLabel":"object"},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6.where","typeLabel":"object[]"}},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.6","typeLabel":"object"},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.7.type","typeLabel":"enum\u003cstring>"},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.7.op","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.7.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"hideRules.when.items.[INDEX].oneOf.7.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.7.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"hideRules.when.items.[INDEX].oneOf.7.target","typeLabel":"object"},"value":{"type":"string","uniqueKey":"hideRules.when.items.[INDEX].oneOf.7.value","typeLabel":"string"}},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.7","typeLabel":"object"},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.8.type","typeLabel":"enum\u003cstring>"},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.8.target.selector","typeLabel":"string"},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string","uniqueKey":"hideRules.when.items.[INDEX].oneOf.8.target.text","typeLabel":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.8.target.nth","typeLabel":"integer"}},"required":["selector"],"uniqueKey":"hideRules.when.items.[INDEX].oneOf.8.target","typeLabel":"object"}},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.8","typeLabel":"object"},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.9.type","typeLabel":"enum\u003cstring>"},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.9.start","typeLabel":"string"},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string","uniqueKey":"hideRules.when.items.[INDEX].oneOf.9.end","typeLabel":"string"}},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.9","typeLabel":"object"},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true,"uniqueKey":"hideRules.when.items.[INDEX].oneOf.10.type","typeLabel":"enum\u003cstring>"},"note":{"type":"string","uniqueKey":"hideRules.when.items.[INDEX].oneOf.10.note","typeLabel":"string"}},"uniqueKey":"hideRules.when.items.[INDEX].oneOf.10","typeLabel":"object"}],"uniqueKey":"hideRules.when.items.[INDEX]","typeLabel":"object"},"uniqueKey":"hideRules.when","typeLabel":"object[]"}},"description":"While `when` matches, on-screen content of this version is hidden — the session is SUSPENDED, not ended: when the conditions stop matching the same session reappears at the same step. Use start-rule conditions to keep content from starting; use hide rules to blank it in specific places (e.g. a settings page).","uniqueKey":"hideRules","typeLabel":"object"},"data":{"description":"Decompiled type-specific body for non-flow content — the same six per-type shapes as the write `data` (that schema is the field dictionary), fully populated on read. Present only when the `data` expand is requested; a flow has no `data` — its body is `steps`.","uniqueKey":"data","typeLabel":"any"},"localizations":{"description":"Per-locale translation status — present only when the `localizations` expand is requested on the content-versions endpoints. One entry per non-default project locale, including locales this version was never translated into.","type":"array","items":{"type":"object","properties":{"code":{"type":"string","isRequired":true,"uniqueKey":"localizations.items.[INDEX].code","typeLabel":"string"},"name":{"type":"string","isRequired":true,"uniqueKey":"localizations.items.[INDEX].name","typeLabel":"string"},"enabled":{"type":"boolean","isRequired":true,"uniqueKey":"localizations.items.[INDEX].enabled","typeLabel":"boolean"},"missing":{"type":"number","isRequired":true,"uniqueKey":"localizations.items.[INDEX].missing","typeLabel":"number"},"outdated":{"type":"number","isRequired":true,"uniqueKey":"localizations.items.[INDEX].outdated","typeLabel":"number"}},"required":["code","name","enabled","missing","outdated"],"uniqueKey":"localizations.items.[INDEX]","typeLabel":"object"},"uniqueKey":"localizations","typeLabel":"object[]"},"scheduledAt":{"description":"Announcement versions only: the \"announcement time\" gating feed visibility and ordering. Absent on other content types and until set (publish stamps it when the author left it null, so published announcements always carry one).","type":"string","uniqueKey":"scheduledAt","typeLabel":"string"}},"required":["id","object","number","firstPublishedAt","themeId","questions","updatedAt","createdAt"],"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"id":"\u003cstring>","object":"contentVersion","number":123,"firstPublishedAt":"\u003cstring>","themeId":"\u003cstring>","questions":[{"object":"question","cvid":"\u003cstring>","name":"\u003cstring>","type":"nps"}],"updatedAt":"2023-11-07T05:31:56Z","createdAt":"2023-11-07T05:31:56Z","steps":[{"object":"step","id":"\u003cstring>","cvid":"\u003cstring>","name":"\u003cstring>","type":"tooltip","sequence":123,"themeId":"\u003cstring>","content":[{"type":"text","markdown":"\u003cstring>","object":"block","id":"\u003cstring>"}],"target":{"selector":"\u003cstring>","text":"\u003cstring>","nth":2},"placement":{"side":"top","align":"start","sideOffset":123,"alignOffset":123,"alignType":"auto","backdrop":true,"blockTarget":true},"width":123,"skippable":true,"explicitCompletionStep":true,"triggers":[{"do":[{"type":"goto_step","step":"\u003cstring>"}],"when":[{"type":"group","match":"all","conditions":"\u003carray>"}],"waitSeconds":123}],"onClick":[{"type":"goto_step","step":"\u003cstring>"}],"advanced":{"hasUnsupported":true}}],"startRules":{"when":[{"type":"group","match":"all","conditions":"\u003carray>"}],"frequency":{"mode":"once","every":{"duration":123,"unit":"seconds","times":123},"atLeast":{"duration":123,"unit":"seconds"}},"priority":"highest","waitSeconds":123,"startIfNotComplete":true},"hideRules":{"when":[{"type":"group","match":"all","conditions":"\u003carray>"}]},"data":"\u003cunknown>","localizations":[{"code":"\u003cstring>","name":"\u003cstring>","enabled":true,"missing":123,"outdated":123}],"scheduledAt":"\u003cstring>"}}},"exampleType":"generated"}},"exampleType":"generated"},"824a67fa-71bd-4a90-b850-86610eef75a1":{"description":"Invalid request — E1017 validation (may carry `issues`; an invalid orderBy/limit is also E1017), E1015 invalid scope, E0003 invalid against current domain state.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code (e.g. E1017). Match on this, never on `message`.","uniqueKey":"error.code","typeLabel":"string"},"message":{"type":"string","description":"Human-readable summary. Wording may change between releases.","uniqueKey":"error.message","typeLabel":"string"},"issues":{"description":"Validation errors (E1017) may carry one entry per problem so every field can be fixed in a single round-trip. Absent on other errors.","type":"array","items":{"type":"object","properties":{"rule":{"type":"string","description":"Which validation layer rejected it: schema | reactive_condition | action_not_allowed | step_shape | reference_target | auto_start | media_url | destination_url. New values may be added; treat an unknown value as a generic validation failure.","isRequired":true,"uniqueKey":"error.issues.items.[INDEX].rule","typeLabel":"string"},"message":{"type":"string","isRequired":true,"uniqueKey":"error.issues.items.[INDEX].message","typeLabel":"string"},"path":{"description":"Path into the request body (e.g. `steps[0].triggers[0].when[1]`).","type":"string","uniqueKey":"error.issues.items.[INDEX].path","typeLabel":"string"}},"required":["rule","message"],"uniqueKey":"error.issues.items.[INDEX]","typeLabel":"object"},"uniqueKey":"error.issues","typeLabel":"object[]"},"doc_url":{"type":"string","description":"Base URL of the API documentation.","uniqueKey":"error.doc_url","typeLabel":"string"}},"isRequired":true,"uniqueKey":"error","typeLabel":"object"}},"required":["error"],"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"error":{"code":"\u003cstring>","message":"\u003cstring>","issues":[{"rule":"\u003cstring>","message":"\u003cstring>","path":"\u003cstring>"}],"doc_url":"\u003cstring>"}}}},"exampleType":"generated"}},"exampleType":"generated"},"688e5ec4-d5d5-43e8-8213-29b3d8e5c069":{"description":"Missing or expired API key — E1010, E1020.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code (e.g. E1017). Match on this, never on `message`.","uniqueKey":"error.code","typeLabel":"string"},"message":{"type":"string","description":"Human-readable summary. Wording may change between releases.","uniqueKey":"error.message","typeLabel":"string"},"issues":{"description":"Validation errors (E1017) may carry one entry per problem so every field can be fixed in a single round-trip. Absent on other errors.","type":"array","items":{"type":"object","properties":{"rule":{"type":"string","description":"Which validation layer rejected it: schema | reactive_condition | action_not_allowed | step_shape | reference_target | auto_start | media_url | destination_url. New values may be added; treat an unknown value as a generic validation failure.","isRequired":true,"uniqueKey":"error.issues.items.[INDEX].rule","typeLabel":"string"},"message":{"type":"string","isRequired":true,"uniqueKey":"error.issues.items.[INDEX].message","typeLabel":"string"},"path":{"description":"Path into the request body (e.g. `steps[0].triggers[0].when[1]`).","type":"string","uniqueKey":"error.issues.items.[INDEX].path","typeLabel":"string"}},"required":["rule","message"],"uniqueKey":"error.issues.items.[INDEX]","typeLabel":"object"},"uniqueKey":"error.issues","typeLabel":"object[]"},"doc_url":{"type":"string","description":"Base URL of the API documentation.","uniqueKey":"error.doc_url","typeLabel":"string"}},"isRequired":true,"uniqueKey":"error","typeLabel":"object"}},"required":["error"],"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"error":{"code":"\u003cstring>","message":"\u003cstring>","issues":[{"rule":"\u003cstring>","message":"\u003cstring>","path":"\u003cstring>"}],"doc_url":"\u003cstring>"}}}},"exampleType":"generated"}},"exampleType":"generated"},"f006b1c3-736f-4f14-a8d4-baeb00161fce":{"description":"Refused — E1000 invalid key, E1011 project not in token scope, E1012 insufficient scope, E1029 environment not in token scope, E1032 environment creation needs a token without env-targeted capabilities (its allowlist cannot cover a not-yet-existing environment), E0043 the project's plan does not include the feature (e.g. webhooks require a paid plan on cloud).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code (e.g. E1017). Match on this, never on `message`.","uniqueKey":"error.code","typeLabel":"string"},"message":{"type":"string","description":"Human-readable summary. Wording may change between releases.","uniqueKey":"error.message","typeLabel":"string"},"issues":{"description":"Validation errors (E1017) may carry one entry per problem so every field can be fixed in a single round-trip. Absent on other errors.","type":"array","items":{"type":"object","properties":{"rule":{"type":"string","description":"Which validation layer rejected it: schema | reactive_condition | action_not_allowed | step_shape | reference_target | auto_start | media_url | destination_url. New values may be added; treat an unknown value as a generic validation failure.","isRequired":true,"uniqueKey":"error.issues.items.[INDEX].rule","typeLabel":"string"},"message":{"type":"string","isRequired":true,"uniqueKey":"error.issues.items.[INDEX].message","typeLabel":"string"},"path":{"description":"Path into the request body (e.g. `steps[0].triggers[0].when[1]`).","type":"string","uniqueKey":"error.issues.items.[INDEX].path","typeLabel":"string"}},"required":["rule","message"],"uniqueKey":"error.issues.items.[INDEX]","typeLabel":"object"},"uniqueKey":"error.issues","typeLabel":"object[]"},"doc_url":{"type":"string","description":"Base URL of the API documentation.","uniqueKey":"error.doc_url","typeLabel":"string"}},"isRequired":true,"uniqueKey":"error","typeLabel":"object"}},"required":["error"],"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"error":{"code":"\u003cstring>","message":"\u003cstring>","issues":[{"rule":"\u003cstring>","message":"\u003cstring>","path":"\u003cstring>"}],"doc_url":"\u003cstring>"}}}},"exampleType":"generated"}},"exampleType":"generated"},"141ac6ec-b62d-4695-a706-216d131d2127":{"description":"Content version not found","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code (e.g. E1017). Match on this, never on `message`.","uniqueKey":"error.code","typeLabel":"string"},"message":{"type":"string","description":"Human-readable summary. Wording may change between releases.","uniqueKey":"error.message","typeLabel":"string"},"issues":{"description":"Validation errors (E1017) may carry one entry per problem so every field can be fixed in a single round-trip. Absent on other errors.","type":"array","items":{"type":"object","properties":{"rule":{"type":"string","description":"Which validation layer rejected it: schema | reactive_condition | action_not_allowed | step_shape | reference_target | auto_start | media_url | destination_url. New values may be added; treat an unknown value as a generic validation failure.","isRequired":true,"uniqueKey":"error.issues.items.[INDEX].rule","typeLabel":"string"},"message":{"type":"string","isRequired":true,"uniqueKey":"error.issues.items.[INDEX].message","typeLabel":"string"},"path":{"description":"Path into the request body (e.g. `steps[0].triggers[0].when[1]`).","type":"string","uniqueKey":"error.issues.items.[INDEX].path","typeLabel":"string"}},"required":["rule","message"],"uniqueKey":"error.issues.items.[INDEX]","typeLabel":"object"},"uniqueKey":"error.issues","typeLabel":"object[]"},"doc_url":{"type":"string","description":"Base URL of the API documentation.","uniqueKey":"error.doc_url","typeLabel":"string"}},"isRequired":true,"uniqueKey":"error","typeLabel":"object"}},"required":["error"],"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"error":{"code":"\u003cstring>","message":"\u003cstring>","issues":[{"rule":"\u003cstring>","message":"\u003cstring>","path":"\u003cstring>"}],"doc_url":"\u003cstring>"}}}},"exampleType":"generated"}},"exampleType":"generated"},"a168bc15-666e-4bd4-a154-fcd38442d008":{"description":"E0049 the version is published (read-only — fork an editable draft first), or E0050 it was modified concurrently (re-read and retry).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code (e.g. E1017). Match on this, never on `message`.","uniqueKey":"error.code","typeLabel":"string"},"message":{"type":"string","description":"Human-readable summary. Wording may change between releases.","uniqueKey":"error.message","typeLabel":"string"},"issues":{"description":"Validation errors (E1017) may carry one entry per problem so every field can be fixed in a single round-trip. Absent on other errors.","type":"array","items":{"type":"object","properties":{"rule":{"type":"string","description":"Which validation layer rejected it: schema | reactive_condition | action_not_allowed | step_shape | reference_target | auto_start | media_url | destination_url. New values may be added; treat an unknown value as a generic validation failure.","isRequired":true,"uniqueKey":"error.issues.items.[INDEX].rule","typeLabel":"string"},"message":{"type":"string","isRequired":true,"uniqueKey":"error.issues.items.[INDEX].message","typeLabel":"string"},"path":{"description":"Path into the request body (e.g. `steps[0].triggers[0].when[1]`).","type":"string","uniqueKey":"error.issues.items.[INDEX].path","typeLabel":"string"}},"required":["rule","message"],"uniqueKey":"error.issues.items.[INDEX]","typeLabel":"object"},"uniqueKey":"error.issues","typeLabel":"object[]"},"doc_url":{"type":"string","description":"Base URL of the API documentation.","uniqueKey":"error.doc_url","typeLabel":"string"}},"isRequired":true,"uniqueKey":"error","typeLabel":"object"}},"required":["error"],"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"error":{"code":"\u003cstring>","message":"\u003cstring>","issues":[{"rule":"\u003cstring>","message":"\u003cstring>","path":"\u003cstring>"}],"doc_url":"\u003cstring>"}}}},"exampleType":"generated"}},"exampleType":"generated"},"c143e5cc-ab03-4988-af15-efb61ab472f5":{"description":"Rate limit exceeded — E1013. The limit follows the project's plan (100/500/1000/3000 requests per minute); unknown credentials share a per-IP bucket. Every response also carries X-RateLimit-Limit / -Remaining / -Reset for pacing; a 429 adds the standard Retry-After header (seconds to back off).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Stable machine-readable code (e.g. E1017). Match on this, never on `message`.","uniqueKey":"error.code","typeLabel":"string"},"message":{"type":"string","description":"Human-readable summary. Wording may change between releases.","uniqueKey":"error.message","typeLabel":"string"},"issues":{"description":"Validation errors (E1017) may carry one entry per problem so every field can be fixed in a single round-trip. Absent on other errors.","type":"array","items":{"type":"object","properties":{"rule":{"type":"string","description":"Which validation layer rejected it: schema | reactive_condition | action_not_allowed | step_shape | reference_target | auto_start | media_url | destination_url. New values may be added; treat an unknown value as a generic validation failure.","isRequired":true,"uniqueKey":"error.issues.items.[INDEX].rule","typeLabel":"string"},"message":{"type":"string","isRequired":true,"uniqueKey":"error.issues.items.[INDEX].message","typeLabel":"string"},"path":{"description":"Path into the request body (e.g. `steps[0].triggers[0].when[1]`).","type":"string","uniqueKey":"error.issues.items.[INDEX].path","typeLabel":"string"}},"required":["rule","message"],"uniqueKey":"error.issues.items.[INDEX]","typeLabel":"object"},"uniqueKey":"error.issues","typeLabel":"object[]"},"doc_url":{"type":"string","description":"Base URL of the API documentation.","uniqueKey":"error.doc_url","typeLabel":"string"}},"isRequired":true,"uniqueKey":"error","typeLabel":"object"}},"required":["error"],"uniqueKey":"","typeLabel":"object"},"examples":{"Example":{"title":"Example","value":{"error":{"code":"\u003cstring>","message":"\u003cstring>","issues":[{"rule":"\u003cstring>","message":"\u003cstring>","path":"\u003cstring>"}],"doc_url":"\u003cstring>"}}}},"exampleType":"generated"}},"exampleType":"generated"}},"security":{"c10e290b-8d50-4a5f-859e-4b77d9e48be0":{"bearer":{"scheme":"bearer","bearerFormat":"utp_... personal API token (opaque)","type":"http","scopes":[],"schemeName":"bearer"}}},"servers":{"23c31da0-5340-453a-ad80-9d71f43aa3ab":{"url":"https://api.usertour.io"}},"schemas":{"f680dfc0-67a4-442d-af6e-7aa79ea787a8":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true},"attribute":{"type":"string","isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true},"segment":{"type":"string","isRequired":true},"in":{"type":"boolean","isRequired":true}}},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true},"includes":{"type":"array","items":{"type":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]}}},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true}}},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true},"event":{"type":"string","isRequired":true},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["op","n"]},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true},"value":{"type":"number"},"value2":{"type":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"]}},"required":["op"]},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"]},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true},"attribute":{"type":"string","isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true},"value":{"type":"string"},"value2":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}}]}}}},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]},"value":{"type":"string"}}},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]}}},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string"}}},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true},"note":{"type":"string"}}}]},"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true},"attribute":{"type":"string","isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true},"value":{"type":"string"},"value2":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}}]},"b55c69d5-0b98-4319-a584-ae0961850a0b":{"oneOf":[{"type":"object","required":["type","markdown"],"properties":{"type":{"type":"string","enum":["text"],"isRequired":true},"markdown":{"type":"string","description":"A small markdown subset: paragraphs, `# `/`## ` headings (h1/h2 only — no h3+), `-`/`*` and `1.` lists, ``` code fences; inline `**bold**`, `*italic*`, `[text](url)`, and `{{ attribute_code | default: \"x\" }}` for user attributes. Anything outside this subset is SILENTLY normalized, not rejected: h3+ → h2; blockquotes flatten to paragraphs; tables, horizontal rules, strikethrough, inline images/code, and liquid filters other than `default` are dropped. Unsupported syntax won't round-trip — don't rely on it.","isRequired":true},"object":{"default":"block","type":"string","enum":["block"]},"id":{"type":"string"}}},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["image"],"isRequired":true},"url":{"type":"string","minLength":1,"isRequired":true},"object":{"default":"block","type":"string","enum":["block"]},"id":{"type":"string"},"alt":{"type":"string"},"link":{"type":"object","properties":{"url":{"type":"string","isRequired":true},"newTab":{"type":"boolean"}},"required":["url"]},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true},"value":{"type":"number","minimum":0}},"required":["unit"]},"margin":{"type":"object","properties":{"enabled":{"type":"boolean"},"top":{"type":"number"},"bottom":{"type":"number"},"left":{"type":"number"},"right":{"type":"number"}}}}},{"type":"object","required":["type","text"],"properties":{"type":{"type":"string","enum":["button"],"isRequired":true},"text":{"type":"string","isRequired":true},"object":{"default":"block","type":"string","enum":["block"]},"id":{"type":"string"},"actions":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true}}},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string"}}},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean"}}},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true}}},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true},"script":{"type":"string","isRequired":true}}},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true},"note":{"type":"string"}}}]}},"disabledWhen":{"description":"REACTIVE slot — polled live in the browser (the button disables the moment the conditions match). Client-evaluable condition types only: attribute / current_url / element / text_input / text_filled / time_window; event / segment / content_state are rejected (E1017).","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true},"attribute":{"type":"string","isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true},"segment":{"type":"string","isRequired":true},"in":{"type":"boolean","isRequired":true}}},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true},"includes":{"type":"array","items":{"type":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]}}},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true}}},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true},"event":{"type":"string","isRequired":true},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["op","n"]},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true},"value":{"type":"number"},"value2":{"type":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"]}},"required":["op"]},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"]},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true},"attribute":{"type":"string","isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true},"value":{"type":"string"},"value2":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}}]}}}},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]},"value":{"type":"string"}}},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]}}},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string"}}},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true},"note":{"type":"string"}}}]}},"hiddenWhen":{"description":"REACTIVE slot — polled live in the browser (the button shows/hides as conditions change). Same client-evaluable-only rule as `disabledWhen`.","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true},"attribute":{"type":"string","isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true},"segment":{"type":"string","isRequired":true},"in":{"type":"boolean","isRequired":true}}},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true},"includes":{"type":"array","items":{"type":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]}}},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true}}},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true},"event":{"type":"string","isRequired":true},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["op","n"]},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true},"value":{"type":"number"},"value2":{"type":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"]}},"required":["op"]},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"]},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true},"attribute":{"type":"string","isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true},"value":{"type":"string"},"value2":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}}]}}}},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]},"value":{"type":"string"}}},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]}}},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string"}}},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true},"note":{"type":"string"}}}]}},"variant":{"type":"string","enum":["primary","secondary"]},"margin":{"type":"object","properties":{"enabled":{"type":"boolean"},"top":{"type":"number"},"bottom":{"type":"number"},"left":{"type":"number"},"right":{"type":"number"}}}}},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["embed"],"isRequired":true},"url":{"type":"string","minLength":1,"description":"Paste the page's normal URL (e.g. a youtube.com/watch link) — on write it is resolved through the standard oEmbed provider registry (YouTube, Vimeo, Loom, Figma, …) and the provider's official embed markup is stored, so you never hand-build /embed/ URLs. A URL no provider claims is iframed as-is — it renders only if that site allows being framed (no X-Frame-Options/CSP block).","isRequired":true},"object":{"default":"block","type":"string","enum":["block"]},"id":{"type":"string"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true},"value":{"type":"number","minimum":0}},"required":["unit"]},"height":{"description":"Embed height. Optional ONLY for provider embeds (YouTube/Vimeo/… size themselves by aspect ratio); a URL with NO oEmbed provider has no ratio, and omitting a pixel height leaves the iframe at the browser's built-in default — a strip ~150px tall, almost never the intended size (validate warns). For plain-iframe URLs always set `{ \"unit\": \"pixels\", \"value\": … }`.","type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true},"value":{"type":"number","minimum":0}},"required":["unit"]},"margin":{"type":"object","properties":{"enabled":{"type":"boolean"},"top":{"type":"number"},"bottom":{"type":"number"},"left":{"type":"number"},"right":{"type":"number"}}}}},{"type":"object","required":["type","question"],"properties":{"type":{"type":"string","enum":["question"],"isRequired":true},"question":{"oneOf":[{"type":"object","required":["kind","name"],"properties":{"kind":{"type":"string","enum":["nps"],"isRequired":true},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true},"cvid":{"type":"string"},"lowLabel":{"type":"string"},"highLabel":{"type":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string"}},"isRequired":true},{"type":"object","required":["kind","name","style","range"],"properties":{"kind":{"type":"string","enum":["rating"],"isRequired":true},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true},"style":{"type":"string","enum":["star","scale"],"description":"star = star rating; scale = a numeric scale. A \"scale\" question IS a rating with style:\"scale\" — there is no separate \"scale\" kind.","isRequired":true},"range":{"type":"object","properties":{"low":{"type":"number","isRequired":true},"high":{"type":"number","isRequired":true}},"required":["low","high"],"description":"Numeric range, e.g. { low: 1, high: 5 }.","isRequired":true},"cvid":{"type":"string"},"lowLabel":{"type":"string"},"highLabel":{"type":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string"}},"isRequired":true},{"type":"object","required":["kind","name","multiline"],"properties":{"kind":{"type":"string","enum":["text"],"isRequired":true},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true},"multiline":{"type":"boolean","isRequired":true},"cvid":{"type":"string"},"placeholder":{"type":"string"},"buttonText":{"type":"string"},"required":{"description":"Require an answer before submit. ONLY `text` supports this — nps / rating / choice cannot be marked required.","type":"boolean"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string"}},"isRequired":true},{"type":"object","required":["kind","name","options","allowMultiple"],"properties":{"kind":{"type":"string","enum":["choice"],"isRequired":true},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true},"options":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string","isRequired":true},"value":{"type":"string","isRequired":true}},"required":["label","value"]},"description":"Each option has a human-facing `label` and a stored `value` — the `value` is what gets recorded/bound as the answer.","isRequired":true},"allowMultiple":{"type":"boolean","description":"false = single-select, true = multi-select. A multi-select answer needs a `list`-typed bound attribute.","isRequired":true},"cvid":{"type":"string"},"enableOther":{"type":"boolean"},"otherPlaceholder":{"type":"string"},"shuffle":{"type":"boolean"},"buttonText":{"type":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string"}},"isRequired":true}],"isRequired":true},"object":{"default":"block","type":"string","enum":["block"]},"id":{"type":"string"},"actions":{"description":"Actions that fire when this question is answered (on pick for nps/rating/single-select; on its Submit button for text/multi-select). Put a `goto_step` HERE to advance to the next step — without it the question records the answer but the flow does NOT advance (validate flags the next step \"not reachable\"). Do NOT add a separate `button` block just to advance: it doubles up with the question's own submit affordance.","type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true}}},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string"}}},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean"}}},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true}}},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true},"script":{"type":"string","isRequired":true}}},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true},"note":{"type":"string"}}}]}}}},{"type":"object","description":"Lays 2+ blocks out side by side in one row. Top-level `content` blocks otherwise stack vertically — each is its own full-width row — so two `button` blocks placed directly in `content` render as two stacked rows, NOT a button pair. For a modal/tooltip footer with a Skip + primary button: one `columns` block with two `{ width: { unit: \"fill\" }, blocks: [...] }` entries, one button per column.","required":["type","columns"],"properties":{"type":{"type":"string","enum":["columns"],"isRequired":true},"columns":{"type":"array","items":{"type":"object","properties":{"blocks":{"type":"array","items":{"$circularRef":"b55c69d5-0b98-4319-a584-ae0961850a0b"},"isRequired":true},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels","fill"],"isRequired":true},"value":{"type":"number","minimum":0}},"required":["unit"]},"justify":{"description":"Horizontal alignment of this column's own block(s) within the column's width — use \"end\"/\"center\" to right-align or center a lone button (a single-column `columns` block).","type":"string","enum":["start","center","end","between","around","evenly"]},"align":{"type":"string","enum":["start","center","end","baseline"]},"padding":{"type":"object","properties":{"enabled":{"type":"boolean"},"top":{"type":"number"},"bottom":{"type":"number"},"left":{"type":"number"},"right":{"type":"number"}}}},"required":["blocks"]},"description":"One entry per column, laid out left-to-right. Each column is a mini vertical stack of `blocks` (usually one).","isRequired":true},"object":{"default":"block","type":"string","enum":["block"]},"id":{"type":"string"}}},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true},"object":{"default":"block","type":"string","enum":["block"]},"id":{"type":"string"},"note":{"type":"string"}}}]},"9f4acbd4-53af-453b-8d42-16f10d97fbfb":{"oneOf":[{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["task_clicked"],"isRequired":true}}},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"9f4acbd4-53af-453b-8d42-16f10d97fbfb"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"f680dfc0-67a4-442d-af6e-7aa79ea787a8"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true},"attribute":{"type":"string","isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true},"segment":{"type":"string","isRequired":true},"in":{"type":"boolean","isRequired":true}}},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true},"includes":{"type":"array","items":{"type":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]}}},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true}}},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true},"event":{"type":"string","isRequired":true},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["op","n"]},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true},"value":{"type":"number"},"value2":{"type":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"]}},"required":["op"]},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"]},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true},"attribute":{"type":"string","isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true},"value":{"type":"string"},"value2":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"8fe387b0-6b7f-4b5d-bc86-69fa333e2df7"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}}]}}}},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]},"value":{"type":"string"}}},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]}}},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string"}}},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true},"note":{"type":"string"}}}]},"35ab8279-f569-41c3-8191-173d22799444":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"35ab8279-f569-41c3-8191-173d22799444"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true},"attribute":{"type":"string","isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true},"segment":{"type":"string","isRequired":true},"in":{"type":"boolean","isRequired":true}}},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true},"includes":{"type":"array","items":{"type":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]}}},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true}}},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true},"event":{"type":"string","isRequired":true},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["op","n"]},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true},"value":{"type":"number"},"value2":{"type":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"]}},"required":["op"]},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"]},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true},"attribute":{"type":"string","isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true},"value":{"type":"string"},"value2":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"4abbcf19-e35d-4250-a5ae-cddc55893958"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}}]}}}},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]},"value":{"type":"string"}}},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]}}},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string"}}},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true},"note":{"type":"string"}}}]},"4abbcf19-e35d-4250-a5ae-cddc55893958":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true},"attribute":{"type":"string","isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true},"value":{"type":"string"},"value2":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"4abbcf19-e35d-4250-a5ae-cddc55893958"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}}]},"05a3f782-7797-4a54-937f-60ab56852e98":{"oneOf":[{"type":"object","required":["type","markdown"],"properties":{"type":{"type":"string","enum":["text"],"isRequired":true},"markdown":{"type":"string","description":"A small markdown subset: paragraphs, `# `/`## ` headings (h1/h2 only — no h3+), `-`/`*` and `1.` lists, ``` code fences; inline `**bold**`, `*italic*`, `[text](url)`, and `{{ attribute_code | default: \"x\" }}` for user attributes. Anything outside this subset is SILENTLY normalized, not rejected: h3+ → h2; blockquotes flatten to paragraphs; tables, horizontal rules, strikethrough, inline images/code, and liquid filters other than `default` are dropped. Unsupported syntax won't round-trip — don't rely on it.","isRequired":true},"object":{"default":"block","type":"string","enum":["block"]},"id":{"type":"string"}}},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["image"],"isRequired":true},"url":{"type":"string","minLength":1,"isRequired":true},"object":{"default":"block","type":"string","enum":["block"]},"id":{"type":"string"},"alt":{"type":"string"},"link":{"type":"object","properties":{"url":{"type":"string","isRequired":true},"newTab":{"type":"boolean"}},"required":["url"]},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true},"value":{"type":"number","minimum":0}},"required":["unit"]},"margin":{"type":"object","properties":{"enabled":{"type":"boolean"},"top":{"type":"number"},"bottom":{"type":"number"},"left":{"type":"number"},"right":{"type":"number"}}}}},{"type":"object","required":["type","text"],"properties":{"type":{"type":"string","enum":["button"],"isRequired":true},"text":{"type":"string","isRequired":true},"object":{"default":"block","type":"string","enum":["block"]},"id":{"type":"string"},"actions":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true}}},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string"}}},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean"}}},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true}}},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true},"script":{"type":"string","isRequired":true}}},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true},"note":{"type":"string"}}}]}},"disabledWhen":{"description":"REACTIVE slot — polled live in the browser (the button disables the moment the conditions match). Client-evaluable condition types only: attribute / current_url / element / text_input / text_filled / time_window; event / segment / content_state are rejected (E1017).","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"35ab8279-f569-41c3-8191-173d22799444"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true},"attribute":{"type":"string","isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true},"segment":{"type":"string","isRequired":true},"in":{"type":"boolean","isRequired":true}}},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true},"includes":{"type":"array","items":{"type":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]}}},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true}}},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true},"event":{"type":"string","isRequired":true},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["op","n"]},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true},"value":{"type":"number"},"value2":{"type":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"]}},"required":["op"]},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"]},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true},"attribute":{"type":"string","isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true},"value":{"type":"string"},"value2":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"4abbcf19-e35d-4250-a5ae-cddc55893958"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}}]}}}},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]},"value":{"type":"string"}}},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]}}},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string"}}},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true},"note":{"type":"string"}}}]}},"hiddenWhen":{"description":"REACTIVE slot — polled live in the browser (the button shows/hides as conditions change). Same client-evaluable-only rule as `disabledWhen`.","type":"array","items":{"oneOf":[{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"35ab8279-f569-41c3-8191-173d22799444"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}},{"type":"object","required":["type","scope","attribute","op"],"properties":{"type":{"type":"string","enum":["attribute"],"isRequired":true},"scope":{"type":"string","enum":["user","company","companyMembership"],"description":"Which entity owns the attribute — `user` (the end user), `company`, or `companyMembership`. Same value as the attribute definition's `scope` (see the attribute definitions list); required to disambiguate a codeName that exists in more than one scope.","isRequired":true},"attribute":{"type":"string","isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"description":"Operator — the allowed set depends on the attribute dataType. String: is | not | contains | not_contains | starts_with | ends_with | any | empty. Number: is | not | lt | lte | gt | gte | between | any | empty. Boolean: true | false | any | empty. List: includes_any | includes_all | not_includes_any | not_includes_all | any | empty. DateTime: less_than | exactly | more_than (relative — `value` is a number of days) | before | on | after (`value` is an absolute date) | any | empty. The relative ops are ONE-SIDED bounds around (now − N days): `less_than N` = the date is AFTER now−N — so it also matches every FUTURE date, and on a future-dated attribute (a trial end, a renewal date) it is NOT \"within the last N days\"; `more_than N` = the date is BEFORE now−N. **\"Signed up in the last N days\" therefore needs BOTH bounds** — `less_than N` AND `more_than 0` in one `all` group; `less_than N` alone silently includes anyone whose date is in the future (a mis-mapped trial-end column, a clock/timezone slip), and those are exactly the users a new-user audience must not contain (observed in testing). Negative N shifts the bound into the future: the rolling \"within the NEXT 7 days\" window is `less_than` value \"0\" AND `more_than` value \"-7\" (two conditions, both required). The relative ops are DAY-granularity only — no unit field; for hour/minute windows use an `event` condition with a `within` (which has a `unit`).","isRequired":true},"value":{"description":"The comparison value (string / number-as-string / date). Omit for any/empty/true/false.","type":"string"},"value2":{"description":"Upper bound for the `between` operator (`value` is the lower bound).","type":"string"},"values":{"description":"Values for the List operators (includes_any / includes_all / …).","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","segment","in"],"properties":{"type":{"type":"string","enum":["segment"],"isRequired":true},"segment":{"type":"string","isRequired":true},"in":{"type":"boolean","isRequired":true}}},{"type":"object","required":["type","includes"],"properties":{"type":{"type":"string","enum":["current_url"],"isRequired":true},"includes":{"type":"array","items":{"type":"string"},"description":"URL patterns (anchored whole-url match, NOT substring/regex). `*` = wildcard within one url part; `:name` = one path segment. A bare `*` (i.e. `[\"*\"]`) matches EVERY page on every host incl. deep paths — the canonical always-on / whole-site pattern (use it when content should be available everywhere). Scope it down instead with: `*/` (homepage only — path exactly `/`), `*/pricing` (one page), `*/app/*` (a section + below), `host.com/*` (any page on a specific host). Multiple patterns are OR-matched: the URL matches this list if it matches ANY one pattern (so \"/tasks OR /dashboard\" is one condition with both patterns here — no group needed).","isRequired":true},"excludes":{"description":"URL patterns to exclude (same syntax as includes); excludes win over includes.","type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","state"],"properties":{"type":{"type":"string","enum":["element"],"isRequired":true},"state":{"type":"string","enum":["present","hidden","disabled","enabled","clicked","unclicked"],"description":"`present` means NOT CLIPPED AWAY: the element is in the DOM and its box lies inside the viewport / its scroll ancestors — scrolled off-screen or `display:none` never satisfies it (and `hidden` is its negation). It is NOT \"the user can see something there\": an EMPTY, zero-height placeholder node satisfies `present` (observed in testing: a checklist task keyed on an initially-empty `\u003cp>` status line ticked itself the moment the checklist appeared, before the shopper did anything). So do not use element presence as a proxy for \"the app has said something\": most apps keep the container mounted and only fill in its text. Match the TEXT instead (`target.text` + `present`, or the negation trick: the old text `hidden`). Appearances shorter than about a second can be missed entirely. `disabled`/`enabled` read the element disabled state at evaluation time. **`clicked` means \"clicked since page load AND the element is STILL in the DOM right now\"** — both halves, re-checked every evaluation. The click memory latches (the listener attaches the FIRST time the condition is evaluated, so earlier clicks are invisible, and the memory survives a re-render), but the element lookup is redone each poll. Two consequences, one of them silent: (1) **an element that UNMOUNTS on click can NEVER satisfy it** — the click lands, the element vanishes, the lookup fails from then on and the condition stays false forever with no error (observed in testing: a tracker on a button that clears its own toolbar counted ZERO real clicks); (2) an element that unmounts and REMOUNTS satisfies it again, so a tracker gated on it fires once per remount — not once per page load. Unlike `present`, this lookup does NOT require viewport visibility: scrolling the target off-screen keeps `clicked` true. `unclicked` negates the same pair, so it is also false while the element is absent. To count a COMPLETED action, condition on what the app shows afterwards (a success toast, a state change) rather than `clicked` on the button that starts it.","isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]}}},{"type":"object","required":["type","content","state"],"properties":{"type":{"type":"string","enum":["content_state"],"isRequired":true},"content":{"type":"string","description":"contentId of the FLOW or CHECKLIST whose per-user state to check (an id from the content list). Only flows and checklists record this state — referencing a banner / launcher / resource-center / tracker is rejected at write.","isRequired":true},"state":{"type":"string","enum":["seen","unseen","completed","uncompleted","active","inactive"],"description":"The referenced flow/checklist's state for THIS user. seen = started at least once (for a flow, TRUE from the moment it opens; for a checklist, TRUE only once the user EXPANDS the panel — a `initialDisplay: \"button\"` checklist whose launcher is never clicked stays unseen forever); unseen = never started; active = currently open/running; inactive = NOT currently running (covers both never-started and ran-then-closed); completed = reached a goal/completion step; uncompleted = not completed. To gate piece B until flow A has run AND closed (the usual \"show next thing after the welcome flow\" sequencing), use `seen` AND `inactive` together — `seen` alone fires while A is still open (B piles on top), and `completed` alone strands users who skip/dismiss A.","isRequired":true}}},{"type":"object","required":["type","event"],"properties":{"type":{"type":"string","enum":["event"],"isRequired":true},"event":{"type":"string","isRequired":true},"count":{"description":"How many times the event must have occurred. Omit it for the common case \"the event has happened\" (treated as at_least 1). Set `op`/`n` for a threshold (`between` needs `n` and `n2`). `at_least`/`between` require n ≥ 1; use `at_most`/`exactly` with 0 for \"never happened\".","type":"object","properties":{"op":{"type":"string","enum":["at_least","at_most","exactly","between"],"isRequired":true},"n":{"type":"integer","minimum":0,"maximum":9007199254740991,"isRequired":true},"n2":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["op","n"]},"within":{"description":"Optional time window for the event count. Omit it (or use `any_time`) to count over all time — \"the event has ever happened\". Any other `op` (`in_the_last` / `more_than` / `between`) requires BOTH `value` and `unit`, and `between` also `value2` — rejected at write otherwise.","type":"object","properties":{"op":{"type":"string","enum":["in_the_last","more_than","between","any_time"],"isRequired":true},"value":{"type":"number"},"value2":{"type":"number"},"unit":{"type":"string","enum":["seconds","minutes","hours","days"]}},"required":["op"]},"scope":{"description":"Whose event activity to count (default `current_user`). `current_user` = only this user's own events. `current_user_in_company` = this user's events, but counted within their currently-associated company context (needs the user associated to a company via `group()` / the company-membership API). `any_user_in_company` = events by ANY user in this user's company — account-level activity (e.g. \"anyone on the account has done X\"). The two company scopes require the user to be in a company or they never match.","type":"string","enum":["current_user","current_user_in_company","any_user_in_company"]},"where":{"type":"array","items":{"oneOf":[{"type":"object","required":["type","attribute","op"],"properties":{"type":{"type":"string","enum":["event_attribute"],"isRequired":true},"attribute":{"type":"string","isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","any","empty","lt","lte","gt","gte","between","true","false","includes_any","includes_all","not_includes_any","not_includes_all","less_than","exactly","more_than","before","on","after"],"isRequired":true},"value":{"type":"string"},"value2":{"type":"string"},"values":{"type":"array","items":{"type":"string"}}}},{"type":"object","required":["type","match","conditions"],"properties":{"type":{"type":"string","enum":["group"],"isRequired":true},"match":{"type":"string","enum":["all","any"],"isRequired":true},"conditions":{"minItems":1,"type":"array","items":{"$circularRef":"4abbcf19-e35d-4250-a5ae-cddc55893958"},"description":"The grouped conditions — at least one. An EMPTY group is not \"no filter\": it never matches, so next to an AND it makes the whole rule unmatchable, and writing one is rejected. A version saved with an empty group in the BUILDER still reads back with it (validate warns); writing that list back is refused until the group is filled or dropped.","isRequired":true}}}]}}}},{"type":"object","required":["type","op"],"properties":{"type":{"type":"string","enum":["text_input"],"isRequired":true},"op":{"type":"string","enum":["is","not","contains","not_contains","starts_with","ends_with","match","unmatch","any","empty"],"isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]},"value":{"type":"string"}}},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["text_filled"],"isRequired":true},"target":{"type":"object","properties":{"selector":{"type":"string","minLength":1,"description":"A stable CSS selector for the element, on the page or inside a same-origin iframe (written relative to the frame's own document). The runtime targets the FIRST match — so either make the selector unique, or pair a stable non-unique selector with `nth` to pick the intended match. A non-unique selector with no `nth` targets the first (often wrong) element and the tooltip silently won't render.","isRequired":true},"text":{"description":"Optional refinement of `selector`. Requires the targeted element's visible text to equal this (exact match, after trim) — use it to pin a specific content/state (e.g. a stable id whose displayed text you want to match: id + text). It refines the element chosen by `selector`/`nth`; on its own it does NOT search among multiple matches, so disambiguate a non-unique selector with `nth`, not `text` alone.","type":"string"},"nth":{"description":"Optional refinement of `selector`. 0-based index to pick which match when the selector isn't unique — e.g. a stable selector matching 3 elements + `nth: 1` targets the 2nd, giving a unique result. Matches are taken in document order (as they appear in the page HTML). Range 0–4 — only the first 5 matches are addressable; a larger value is rejected.","type":"integer","minimum":0,"maximum":4}},"required":["selector"]}}},{"type":"object","required":["type","start"],"properties":{"type":{"type":"string","enum":["time_window"],"isRequired":true},"start":{"type":"string","description":"Window start (ISO datetime). REQUIRED — the runtime never matches a window without a start, so an end-only window is rejected at write. For \"until X\" semantics, set start to any past instant and end to X.","isRequired":true},"end":{"description":"Window end (ISO datetime). Omit for an open-ended \"from start onwards\" window.","type":"string"}}},{"type":"object","description":"Read-side placeholder for a stored condition this API cannot express (`note` says what it stands for — usually a DEAD condition the runtime never matches: a deleted attribute/event, an end-only time window). It cannot be written back (the placeholder carries no data to preserve): echoing it is rejected. Either remove it from the list you write — an explicit choice that DELETES the stored condition; mind that a never-matching node inside an AND list pins the whole rule to \"never fires\", so deleting it can bring the remaining conditions to life — or repair the original condition in the Usertour builder first.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true},"note":{"type":"string"}}}]}},"variant":{"type":"string","enum":["primary","secondary"]},"margin":{"type":"object","properties":{"enabled":{"type":"boolean"},"top":{"type":"number"},"bottom":{"type":"number"},"left":{"type":"number"},"right":{"type":"number"}}}}},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["embed"],"isRequired":true},"url":{"type":"string","minLength":1,"description":"Paste the page's normal URL (e.g. a youtube.com/watch link) — on write it is resolved through the standard oEmbed provider registry (YouTube, Vimeo, Loom, Figma, …) and the provider's official embed markup is stored, so you never hand-build /embed/ URLs. A URL no provider claims is iframed as-is — it renders only if that site allows being framed (no X-Frame-Options/CSP block).","isRequired":true},"object":{"default":"block","type":"string","enum":["block"]},"id":{"type":"string"},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true},"value":{"type":"number","minimum":0}},"required":["unit"]},"height":{"description":"Embed height. Optional ONLY for provider embeds (YouTube/Vimeo/… size themselves by aspect ratio); a URL with NO oEmbed provider has no ratio, and omitting a pixel height leaves the iframe at the browser's built-in default — a strip ~150px tall, almost never the intended size (validate warns). For plain-iframe URLs always set `{ \"unit\": \"pixels\", \"value\": … }`.","type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels"],"isRequired":true},"value":{"type":"number","minimum":0}},"required":["unit"]},"margin":{"type":"object","properties":{"enabled":{"type":"boolean"},"top":{"type":"number"},"bottom":{"type":"number"},"left":{"type":"number"},"right":{"type":"number"}}}}},{"type":"object","required":["type","question"],"properties":{"type":{"type":"string","enum":["question"],"isRequired":true},"question":{"oneOf":[{"type":"object","required":["kind","name"],"properties":{"kind":{"type":"string","enum":["nps"],"isRequired":true},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true},"cvid":{"type":"string"},"lowLabel":{"type":"string"},"highLabel":{"type":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string"}},"isRequired":true},{"type":"object","required":["kind","name","style","range"],"properties":{"kind":{"type":"string","enum":["rating"],"isRequired":true},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true},"style":{"type":"string","enum":["star","scale"],"description":"star = star rating; scale = a numeric scale. A \"scale\" question IS a rating with style:\"scale\" — there is no separate \"scale\" kind.","isRequired":true},"range":{"type":"object","properties":{"low":{"type":"number","isRequired":true},"high":{"type":"number","isRequired":true}},"required":["low","high"],"description":"Numeric range, e.g. { low: 1, high: 5 }.","isRequired":true},"cvid":{"type":"string"},"lowLabel":{"type":"string"},"highLabel":{"type":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string"}},"isRequired":true},{"type":"object","required":["kind","name","multiline"],"properties":{"kind":{"type":"string","enum":["text"],"isRequired":true},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true},"multiline":{"type":"boolean","isRequired":true},"cvid":{"type":"string"},"placeholder":{"type":"string"},"buttonText":{"type":"string"},"required":{"description":"Require an answer before submit. ONLY `text` supports this — nps / rating / choice cannot be marked required.","type":"boolean"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string"}},"isRequired":true},{"type":"object","required":["kind","name","options","allowMultiple"],"properties":{"kind":{"type":"string","enum":["choice"],"isRequired":true},"name":{"type":"string","description":"The question's internal name / analytics label (it is the `questionName` on captured responses). It is NOT rendered to the user — the widget shows only the input (scale / options / text field), not this string. To show a visible question prompt, add a `text` block in the SAME step before the question block; a question with only a `name` renders as bare options with no question text.","isRequired":true},"options":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string","isRequired":true},"value":{"type":"string","isRequired":true}},"required":["label","value"]},"description":"Each option has a human-facing `label` and a stored `value` — the `value` is what gets recorded/bound as the answer.","isRequired":true},"allowMultiple":{"type":"boolean","description":"false = single-select, true = multi-select. A multi-select answer needs a `list`-typed bound attribute.","isRequired":true},"cvid":{"type":"string"},"enableOther":{"type":"boolean"},"otherPlaceholder":{"type":"string"},"shuffle":{"type":"boolean"},"buttonText":{"type":"string"},"bindAttribute":{"description":"Optional: codeName of an EXISTING attribute (create the attribute definition first) to ALSO save this answer onto the user for targeting/segmentation — use the codeName, NOT the id. The write does not check it, but the version validation WARNS when the attribute is missing or its dataType mismatches the answer — read warnings. A wrong code that slips through silently captures nothing at runtime. Match the attribute dataType to the answer: number (nps / rating), string (single-select choice), list (multi-select choice). Leaving it unset still records the answer as a response event — bind only when you need to target/segment on it.","type":"string"}},"isRequired":true}],"isRequired":true},"object":{"default":"block","type":"string","enum":["block"]},"id":{"type":"string"},"actions":{"description":"Actions that fire when this question is answered (on pick for nps/rating/single-select; on its Submit button for text/multi-select). Put a `goto_step` HERE to advance to the next step — without it the question records the answer but the flow does NOT advance (validate flags the next step \"not reachable\"). Do NOT add a separate `button` block just to advance: it doubles up with the question's own submit affordance.","type":"array","items":{"oneOf":[{"type":"object","required":["type","step"],"properties":{"type":{"type":"string","enum":["goto_step"],"isRequired":true},"step":{"type":"string","description":"Target step: a step `key` declared elsewhere in the same write, or an existing step cvid. Resolved server-side to the cvid.","isRequired":true}}},{"type":"object","required":["type","content"],"properties":{"type":{"type":"string","enum":["start_content"],"isRequired":true},"content":{"type":"string","description":"contentId of the flow or checklist to launch (an id from the content list) — a raw content id, NOT a step key (unlike goto_step). Must reference a flow or checklist (a banner / launcher / resource-center / tracker is rejected at write). The target must be PUBLISHED to actually start at runtime; an unknown/dangling id is rejected at validate.","isRequired":true},"step":{"description":"Optional cvid of a step within the launched flow to start at.","type":"string"}}},{"type":"object","required":["type","url"],"properties":{"type":{"type":"string","enum":["navigate"],"isRequired":true},"url":{"type":"string","description":"Absolute URL, or an app-relative path (\"/docs/x\") resolved against the origin the user is on — relative paths are the normal choice for in-app navigation.","isRequired":true},"newTab":{"description":"Open the URL in a new browser tab instead of navigating the current one.","type":"boolean"}}},{"type":"object","description":"Dismiss the piece the action lives on. NOT accepted everywhere this union appears: a resource center has no dismiss (closing the panel is a UI affordance, not an action) and an announcement is marked seen, never dismissed — writes there are rejected (E1017), not ignored. Flows / checklists / launchers / banners all accept it.","required":["type"],"properties":{"type":{"type":"string","enum":["dismiss"],"isRequired":true}}},{"type":"object","description":"Read-back of an existing script action — scripts are not authorable through this API. Echo it back UNCHANGED (same script) when rewriting the surrounding list and the stored action is preserved; omitting it deletes it (action lists are full replacements). Authoring a new or edited script is rejected.","required":["type","script"],"properties":{"type":{"type":"string","enum":["run_javascript"],"isRequired":true},"script":{"type":"string","isRequired":true}}},{"type":"object","description":"Echo-only placeholder for a stored action this schema cannot express (`note` = internal type). Echo it back to preserve the stored action; writing one fresh is rejected.","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true},"note":{"type":"string"}}}]}}}},{"type":"object","description":"Lays 2+ blocks out side by side in one row. Top-level `content` blocks otherwise stack vertically — each is its own full-width row — so two `button` blocks placed directly in `content` render as two stacked rows, NOT a button pair. For a modal/tooltip footer with a Skip + primary button: one `columns` block with two `{ width: { unit: \"fill\" }, blocks: [...] }` entries, one button per column.","required":["type","columns"],"properties":{"type":{"type":"string","enum":["columns"],"isRequired":true},"columns":{"type":"array","items":{"type":"object","properties":{"blocks":{"type":"array","items":{"$circularRef":"05a3f782-7797-4a54-937f-60ab56852e98"},"isRequired":true},"width":{"type":"object","properties":{"unit":{"type":"string","enum":["percent","pixels","fill"],"isRequired":true},"value":{"type":"number","minimum":0}},"required":["unit"]},"justify":{"description":"Horizontal alignment of this column's own block(s) within the column's width — use \"end\"/\"center\" to right-align or center a lone button (a single-column `columns` block).","type":"string","enum":["start","center","end","between","around","evenly"]},"align":{"type":"string","enum":["start","center","end","baseline"]},"padding":{"type":"object","properties":{"enabled":{"type":"boolean"},"top":{"type":"number"},"bottom":{"type":"number"},"left":{"type":"number"},"right":{"type":"number"}}}},"required":["blocks"]},"description":"One entry per column, laid out left-to-right. Each column is a mini vertical stack of `blocks` (usually one).","isRequired":true},"object":{"default":"block","type":"string","enum":["block"]},"id":{"type":"string"}}},{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["unsupported"],"isRequired":true},"object":{"default":"block","type":"string","enum":["block"]},"id":{"type":"string"},"note":{"type":"string"}}}]}},"processedSecurityOptions":[{"schemes":[{"scheme":{"scheme":"bearer","bearerFormat":"utp_... personal API token (opaque)","type":"http","scopes":[],"schemeName":"bearer"},"schema":{"title":"Bearer Auth","type":"string","isRequired":true,"description":"Bearer authentication header of the form `Bearer \u003ctoken>`, where `\u003ctoken>` is your auth token.","format":"bearer","typeLabel":"string\u003cbearer>","deprecated":false},"schemeName":"bearer","name":"Authorization"}],"title":"bearer"}]},"originalFileLocation":"api-reference-v2/openapi.json"},"subdomain":"usertour","siteName":"Usertour documention","title":"Update a content version - Usertour documention","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-11-10T06:48:43.754Z","originalFileLocation":"api-reference/content-versions/update-a-content-version.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"Write steps, start/hide rules, themeId, or type-specific data to a draft version.\">\u003cmeta data-mint-head name=\"application-name\" content=\"Usertour documention\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/usertour/vM2wRfIVfuMnzscJ/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/usertour/vM2wRfIVfuMnzscJ/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/usertour/vM2wRfIVfuMnzscJ/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/usertour/vM2wRfIVfuMnzscJ/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/usertour/vM2wRfIVfuMnzscJ/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/usertour/vM2wRfIVfuMnzscJ/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/usertour/vM2wRfIVfuMnzscJ/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/usertour/vM2wRfIVfuMnzscJ/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/usertour/vM2wRfIVfuMnzscJ/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Update a content version - Usertour documention\">\u003cmeta data-mint-head property=\"og:description\" content=\"Write steps, start/hide rules, themeId, or type-specific data to a draft version.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Usertour documention\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.usertour.io/api-reference/content-versions/update-a-content-version\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://usertour.mintlify.site/_mintlify/api/og/v2?division=Content+versions\u0026amp;title=Update+a+content+version\u0026amp;description=Write+steps%2C+start%2Fhide+rules%2C+themeId%2C+or+type-specific+data+to+a+draft+version.\u0026amp;theme=51092ed6781dc62f07a52a12\u0026amp;signature=1073f4a614f1231ef87b6bf64552227ea2d773155d6c5d97aca2527b1b6c6b57\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Update a content version - Usertour documention\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Write steps, start/hide rules, themeId, or type-specific data to a draft version.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://usertour.mintlify.site/_mintlify/api/og/v2?division=Content+versions\u0026amp;title=Update+a+content+version\u0026amp;description=Write+steps%2C+start%2Fhide+rules%2C+themeId%2C+or+type-specific+data+to+a+draft+version.\u0026amp;theme=51092ed6781dc62f07a52a12\u0026amp;signature=1073f4a614f1231ef87b6bf64552227ea2d773155d6c5d97aca2527b1b6c6b57\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/usertour/vM2wRfIVfuMnzscJ/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Usertour documention\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#0D9373\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.usertour.io/api-reference/content-versions/update-a-content-version\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/api-reference/content-versions/update-a-content-version.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.usertour.io/#organization\",\"name\":\"Usertour documention\",\"url\":\"https://docs.usertour.io\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/usertour/fGy2waOqk2YYQ2QK/logo-dark.svg?fit=max\u0026auto=format\u0026n=fGy2waOqk2YYQ2QK\u0026q=85\u0026s=841070ff620cff55401ec69cfc514e27\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.usertour.io#website\",\"name\":\"Usertour documention\",\"url\":\"https://docs.usertour.io\",\"publisher\":{\"@id\":\"https://docs.usertour.io/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.usertour.io/api-reference/content-versions/update-a-content-version#webpage\",\"url\":\"https://docs.usertour.io/api-reference/content-versions/update-a-content-version\",\"name\":\"Update a content version\",\"description\":\"Write steps, start/hide rules, themeId, or type-specific data to a draft version.\",\"dateModified\":\"2026-11-10T06:48:43.754Z\",\"isPartOf\":{\"@id\":\"https://docs.usertour.io#website\"},\"breadcrumb\":{\"@id\":\"https://docs.usertour.io/api-reference/content-versions/update-a-content-version#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.usertour.io/api-reference/content-versions/update-a-content-version#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Endpoints\",\"item\":\"https://docs.usertour.io/api-reference/analytics/get-content-analytics\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Content versions\",\"item\":\"https://docs.usertour.io/api-reference/content-versions/list-content-versions\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Update a content version\",\"item\":\"https://docs.usertour.io/api-reference/content-versions/update-a-content-version\"}]},{\"@type\":[\"TechArticle\",\"APIReference\"],\"@id\":\"https://docs.usertour.io/api-reference/content-versions/update-a-content-version#apireference\",\"headline\":\"Update a content version\",\"name\":\"Update a content version\",\"description\":\"Write steps, start/hide rules, themeId, or type-specific data to a draft version.\",\"url\":\"https://docs.usertour.io/api-reference/content-versions/update-a-content-version\",\"mainEntityOfPage\":{\"@id\":\"https://docs.usertour.io/api-reference/content-versions/update-a-content-version#webpage\"},\"dateModified\":\"2026-11-10T06:48:43.754Z\",\"publisher\":{\"@id\":\"https://docs.usertour.io/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.usertour.io#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Usertour documention\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Usertour documention","nav":{"global":null,"anchors":[{"anchor":"Documentation","icon":"book-open","pages":[{"title":"Documentation","href":"/build-onboarding-with-ai"}]},{"anchor":"Usertour.js Reference","icon":"connectdevelop","pages":[{"title":"Usertour.js Reference","href":"/developers/usertourjs-reference/overview"}]},{"anchor":"API Reference","icon":"terminal","groups":[{"group":"Getting started","pages":[{"title":"Introduction","href":"/api-reference-v2/introduction"},{"title":"Authentication","href":"/api-reference-v2/authentication"},{"title":"Errors","href":"/api-reference-v2/errors"},{"title":"MCP server","href":"/api-reference-v2/mcp"}]},{"group":"Conventions","pages":[{"title":"Pagination","href":"/api-reference-v2/pagination"},{"title":"Ordering","href":"/api-reference-v2/ordering"},{"title":"Expanding objects","href":"/api-reference-v2/expanding-objects"},{"title":"Attributes","href":"/api-reference-v2/attributes"}]},{"group":"Concepts","pages":[{"title":"Content representation","href":"/api-reference-v2/content-representation"},{"title":"Blocks","href":"/api-reference-v2/blocks"},{"title":"Conditions \u0026 actions","href":"/api-reference-v2/conditions-and-actions"},{"title":"Start, hide \u0026 trigger rules","href":"/api-reference-v2/rules"},{"title":"Type-specific data","href":"/api-reference-v2/type-data"}]},{"group":"Endpoints","pages":[{"group":"Analytics","pages":[{"title":"Get content analytics","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/content/{id}/analytics","href":"/api-reference/analytics/get-content-analytics"},{"title":"Get question analytics","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/content/{id}/analytics/questions","href":"/api-reference/analytics/get-question-analytics"}]},{"group":"Content","pages":[{"title":"List content","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/content","href":"/api-reference/content/list-content"},{"title":"Create content","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/content","href":"/api-reference/content/create-content"},{"title":"Get content","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/content/{id}","href":"/api-reference/content/get-content"},{"title":"Delete content","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json delete /v2/projects/{projectId}/content/{id}","href":"/api-reference/content/delete-content"},{"title":"Update content","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json patch /v2/projects/{projectId}/content/{id}","href":"/api-reference/content/update-content"},{"title":"Restore deleted content","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/content/{id}/restore","href":"/api-reference/content/restore-deleted-content"},{"title":"Duplicate content","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/content/{id}/duplicate","href":"/api-reference/content/duplicate-content"},{"title":"Publish a version","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/content/{id}/publish","href":"/api-reference/content/publish-a-version"},{"title":"Unpublish a version","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/content/{id}/unpublish","href":"/api-reference/content/unpublish-a-version"}]},{"group":"Content versions","pages":[{"title":"List content versions","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/content/{contentId}/versions","href":"/api-reference/content-versions/list-content-versions"},{"title":"Create a content version","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/content/{contentId}/versions","href":"/api-reference/content-versions/create-a-content-version"},{"title":"Get a content version","description":null,"deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/content/{contentId}/versions/{id}","href":"/api-reference/content-versions/get-a-content-version"},{"title":"Update a content version","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json patch /v2/projects/{projectId}/content/{contentId}/versions/{id}","href":"/api-reference/content-versions/update-a-content-version"},{"title":"Validate a content version","description":"Dry-run usability check: the same rules `publish` enforces, without mutating. Returns errors (these block publish) and advisory warnings.","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/content/{contentId}/versions/{id}/validate","href":"/api-reference/content-versions/validate-a-content-version"},{"title":"Restore a content version","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/content/{contentId}/versions/{id}/restore","href":"/api-reference/content-versions/restore-a-content-version"},{"title":"Get a translation","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/content/{contentId}/versions/{versionId}/localizations/{code}","href":"/api-reference/content-versions/get-a-translation"},{"title":"Update a translation","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json put /v2/projects/{projectId}/content/{contentId}/versions/{versionId}/localizations/{code}","href":"/api-reference/content-versions/update-a-translation"}]},{"group":"Themes","pages":[{"title":"List themes","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/themes","href":"/api-reference/themes/list-themes"},{"title":"Create a theme","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/themes","href":"/api-reference/themes/create-a-theme"},{"title":"Get a theme","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/themes/{id}","href":"/api-reference/themes/get-a-theme"},{"title":"Delete a theme","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json delete /v2/projects/{projectId}/themes/{id}","href":"/api-reference/themes/delete-a-theme"},{"title":"Update a theme","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json patch /v2/projects/{projectId}/themes/{id}","href":"/api-reference/themes/update-a-theme"},{"title":"Duplicate a theme","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/themes/{id}/duplicate","href":"/api-reference/themes/duplicate-a-theme"},{"title":"Restore a deleted theme","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/themes/{id}/restore","href":"/api-reference/themes/restore-a-deleted-theme"}]},{"group":"Users","pages":[{"title":"List users","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/environments/{environmentId}/users","href":"/api-reference/users/list-users"},{"title":"Get a user","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/environments/{environmentId}/users/{id}","href":"/api-reference/users/get-a-user"},{"title":"Create or update a user","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json put /v2/projects/{projectId}/environments/{environmentId}/users/{id}","href":"/api-reference/users/create-or-update-a-user"},{"title":"Delete a user","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json delete /v2/projects/{projectId}/environments/{environmentId}/users/{id}","href":"/api-reference/users/delete-a-user"}]},{"group":"Companies","pages":[{"title":"List companies","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/environments/{environmentId}/companies","href":"/api-reference/companies/list-companies"},{"title":"Get a company","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/environments/{environmentId}/companies/{id}","href":"/api-reference/companies/get-a-company"},{"title":"Create or update a company","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json put /v2/projects/{projectId}/environments/{environmentId}/companies/{id}","href":"/api-reference/companies/create-or-update-a-company"},{"title":"Delete a company","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json delete /v2/projects/{projectId}/environments/{environmentId}/companies/{id}","href":"/api-reference/companies/delete-a-company"},{"title":"Add a company member","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json put /v2/projects/{projectId}/environments/{environmentId}/companies/{id}/memberships/{userId}","href":"/api-reference/companies/add-a-company-member"},{"title":"Remove a company member","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json delete /v2/projects/{projectId}/environments/{environmentId}/companies/{id}/memberships/{userId}","href":"/api-reference/companies/remove-a-company-member"}]},{"group":"Segments","pages":[{"title":"List segments","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/segments","href":"/api-reference/segments/list-segments"},{"title":"Create a segment","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/segments","href":"/api-reference/segments/create-a-segment"},{"title":"Get a segment","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/segments/{id}","href":"/api-reference/segments/get-a-segment"},{"title":"Delete a segment","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json delete /v2/projects/{projectId}/segments/{id}","href":"/api-reference/segments/delete-a-segment"},{"title":"Update a segment","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json patch /v2/projects/{projectId}/segments/{id}","href":"/api-reference/segments/update-a-segment"},{"title":"Restore a deleted segment","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/segments/{id}/restore","href":"/api-reference/segments/restore-a-deleted-segment"},{"title":"Add a segment member","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json put /v2/projects/{projectId}/environments/{environmentId}/segments/{id}/members/{externalId}","href":"/api-reference/segments/add-a-segment-member"},{"title":"Remove a segment member","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json delete /v2/projects/{projectId}/environments/{environmentId}/segments/{id}/members/{externalId}","href":"/api-reference/segments/remove-a-segment-member"}]},{"group":"Sessions","pages":[{"title":"List sessions","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/environments/{environmentId}/sessions","href":"/api-reference/sessions/list-sessions"},{"title":"Get a session","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/environments/{environmentId}/sessions/{id}","href":"/api-reference/sessions/get-a-session"},{"title":"Delete a session","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json delete /v2/projects/{projectId}/environments/{environmentId}/sessions/{id}","href":"/api-reference/sessions/delete-a-session"},{"title":"End a session","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/environments/{environmentId}/sessions/{id}/end","href":"/api-reference/sessions/end-a-session"}]},{"group":"Attribute definitions","pages":[{"title":"List attribute definitions","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/attribute-definitions","href":"/api-reference/attribute-definitions/list-attribute-definitions"},{"title":"Create an attribute definition","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/attribute-definitions","href":"/api-reference/attribute-definitions/create-an-attribute-definition"},{"title":"Get an attribute definition","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/attribute-definitions/{id}","href":"/api-reference/attribute-definitions/get-an-attribute-definition"},{"title":"Delete an attribute definition","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json delete /v2/projects/{projectId}/attribute-definitions/{id}","href":"/api-reference/attribute-definitions/delete-an-attribute-definition"},{"title":"Update an attribute definition","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json patch /v2/projects/{projectId}/attribute-definitions/{id}","href":"/api-reference/attribute-definitions/update-an-attribute-definition"},{"title":"Restore a deleted attribute definition","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/attribute-definitions/{id}/restore","href":"/api-reference/attribute-definitions/restore-a-deleted-attribute-definition"}]},{"group":"Event definitions","pages":[{"title":"List event definitions","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/event-definitions","href":"/api-reference/event-definitions/list-event-definitions"},{"title":"Create an event definition","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/event-definitions","href":"/api-reference/event-definitions/create-an-event-definition"},{"title":"Get an event definition","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/event-definitions/{id}","href":"/api-reference/event-definitions/get-an-event-definition"},{"title":"Delete an event definition","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json delete /v2/projects/{projectId}/event-definitions/{id}","href":"/api-reference/event-definitions/delete-an-event-definition"},{"title":"Update an event definition","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json patch /v2/projects/{projectId}/event-definitions/{id}","href":"/api-reference/event-definitions/update-an-event-definition"},{"title":"Restore a deleted event definition","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/event-definitions/{id}/restore","href":"/api-reference/event-definitions/restore-a-deleted-event-definition"}]},{"group":"Webhooks","pages":[{"title":"List webhooks","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/environments/{environmentId}/webhooks","href":"/api-reference/webhooks/list-webhooks"},{"title":"Create a webhook","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/environments/{environmentId}/webhooks","href":"/api-reference/webhooks/create-a-webhook"},{"title":"Get a webhook (includes the signing secret for webhook:manage tokens)","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/environments/{environmentId}/webhooks/{id}","href":"/api-reference/webhooks/get-a-webhook-includes-the-signing-secret-for-webhook:manage-tokens"},{"title":"Delete a webhook","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json delete /v2/projects/{projectId}/environments/{environmentId}/webhooks/{id}","href":"/api-reference/webhooks/delete-a-webhook"},{"title":"Update a webhook","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json patch /v2/projects/{projectId}/environments/{environmentId}/webhooks/{id}","href":"/api-reference/webhooks/update-a-webhook"},{"title":"Rotate the signing secret","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/environments/{environmentId}/webhooks/{id}/rotate-secret","href":"/api-reference/webhooks/rotate-the-signing-secret"}]},{"group":"Me","pages":[{"title":"Introspect the token","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/me","href":"/api-reference/me/introspect-the-token"}]},{"group":"Localizations","pages":[{"title":"List locales","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/locales","href":"/api-reference/localizations/list-locales"},{"title":"List localizations","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/localizations","href":"/api-reference/localizations/list-localizations"},{"title":"Create a localization","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/localizations","href":"/api-reference/localizations/create-a-localization"},{"title":"Delete a localization","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json delete /v2/projects/{projectId}/localizations/{id}","href":"/api-reference/localizations/delete-a-localization"},{"title":"Update a localization","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json patch /v2/projects/{projectId}/localizations/{id}","href":"/api-reference/localizations/update-a-localization"},{"title":"Restore a deleted localization","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/localizations/{id}/restore","href":"/api-reference/localizations/restore-a-deleted-localization"}]},{"group":"Events","pages":[{"title":"Track an event","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/environments/{environmentId}/events","href":"/api-reference/events/track-an-event"}]},{"group":"Environments","pages":[{"title":"List environments","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/environments","href":"/api-reference/environments/list-environments"},{"title":"Create an environment","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json post /v2/projects/{projectId}/environments","href":"/api-reference/environments/create-an-environment"},{"title":"Get an environment","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json get /v2/projects/{projectId}/environments/{id}","href":"/api-reference/environments/get-an-environment"},{"title":"Delete an environment","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json delete /v2/projects/{projectId}/environments/{id}","href":"/api-reference/environments/delete-an-environment"},{"title":"Update an environment","deprecated":null,"version":null,"openapi":"/api-reference-v2/openapi.json patch /v2/projects/{projectId}/environments/{id}","href":"/api-reference/environments/update-an-environment"}]}]}]},{"anchor":"Self-Hosting","icon":"github","pages":[{"title":"Self-Hosting","href":"/open-source/self-hosting"}]},{"anchor":"Use Cases","icon":"route","pages":[{"title":"Use Cases","href":"/use-cases/analyze-onboarding-with-ai"}]},{"anchor":"API Reference (v1)","icon":"terminal","pages":[{"title":"API Reference (v1)","href":"/api-reference/introduction"}]},{"anchor":"Changelog","icon":"clock-rotate-left","pages":[{"title":"Changelog","href":"/changelog/2026"}]}]},"basePath":"","title":"Update a content version - Usertour documention","buildId":"dpl_42LwxxU1kGN114H4XzwGYXa9ZDah","deploymentId":"dpl_42LwxxU1kGN114H4XzwGYXa9ZDah","dynamics":["c2aa737661","2b790eda92","b0f84f53ff","a9bb46feb3"],"chunks":["OpenApiReferenceProvider-C-hEh3I2.js","utils-CnF4QiUD.js","cloneDeep-izs3Hzxb.js","_Uint8Array-BVIqJ90l.js","isObjectLike-C8vmzPEN.js","isArray-Dcu1dQ5t.js","utils-C7PVNbel.js","toString-BX-iyh8G.js","isSymbol-CSH9U2Gm.js","addUniqueKeysToSchema-8ytevL2A.js","snippetPresets-BT1alx3v.js","style-to-object-BY3JEAdk.js","v4-DIgz8zy6.js","conditional-requirements-CFM0id0Q.js","ApiExamples-BLXC0i_W.js","isEqual-BfaiBbcl.js","usePlaygroundInputsStore-CyLIGI9_.js","shallow-DC0lGVIW.js","getApiPlaygroundDisplay-CgDQ85tN.js","CodeSnippet-6-I-Gpv9.js","CodeSnippets-BAk1kDdu.js","Page-C945cI-W.js","getPastedJson-B276RuWN.js","icon-BqP8etTm.js","MethodPill-DM6sbxG1.js","DuplicateIcon-PyJglOaE.js","createIcon-CMeOMAc_.js","DynamicMarkdown-BoyYAA8K.js","SectionHeading-DAYv3gvZ.js","axios-vUo_bRtp.js","Markdown-BhTTmazV.js","clsx-CR-RHrVC.js","lib-DuNMoZ05.js","lib-B8HbPgcC.js","html-void-elements-mCSPHezU.js","dist-CBUH0-zX.js","katex-YMM-FMeD.js","marked.esm-CZWythHk.js","AssistantPageLink-BmARFYNC.js"]}