@conduction/nextcloud-vue 2.70.0 → 2.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +151 -13
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js +3 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +38 -42
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +29 -2
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js +71 -34
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +22 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnNotesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +46 -32
- package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +30 -9
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
- package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
- package/dist/esm/composables/useWalkthrough.js +11 -3
- package/dist/esm/composables/useWalkthrough.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +53 -0
- package/dist/esm/utils/columnFilters.js +54 -4
- package/dist/esm/utils/columnFilters.js.map +1 -1
- package/dist/esm/utils/userAutocomplete.js +69 -3
- package/dist/esm/utils/userAutocomplete.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +615 -235
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +53 -0
- package/package.json +1 -1
- package/src/components/CnAppRoot/CnAppRoot.vue +153 -13
- package/src/components/CnDashboardPage/CnDashboardPage.vue +20 -18
- package/src/components/CnFormDialog/CnFormDialog.vue +62 -2
- package/src/components/CnIndexPage/CnIndexPage.vue +23 -1
- package/src/components/CnObjectSidebar/CnNotesTab.vue +1 -1
- package/src/components/CnSetupWizard/CnSetupWizard.vue +7 -0
- package/src/components/CnWalkthrough/CnWalkthrough.vue +30 -9
- package/src/composables/useWalkthrough.js +11 -3
- package/src/css/dashboard.css +44 -0
- package/src/schemas/app-manifest-v2.schema.json +2 -1
- package/src/utils/columnFilters.js +54 -4
- package/src/utils/userAutocomplete.js +68 -2
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnWalkthrough.vue3.js","sources":["../../../../src/components/CnWalkthrough/CnWalkthrough.vue?vue&type=template&id=5691d49a&lang.js"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl> -->\n<template>\n\t<div v-if=\"active\"\n\t\tclass=\"cn-walkthrough\"\n\t\t:style=\"{ zIndex }\"\n\t\trole=\"dialog\"\n\t\taria-modal=\"true\"\n\t\t:aria-label=\"dialogLabel\">\n\t\t<!-- aria-live announcement of the current step -->\n\t\t<div class=\"cn-walkthrough__live\" aria-live=\"polite\">\n\t\t\t{{ liveText }}\n\t\t</div>\n\n\t\t<!-- Dimmer: a centered step uses one full overlay; an anchored step uses\n\t\t four strips that frame the target rect, leaving a real interactive hole.\n\t\t An ANCHORED step that lost its anchor (target present but not laid\n\t\t out, e.g. inside a closed settings foldout) falls back to the full\n\t\t overlay too — but there it must let clicks THROUGH: the step's own\n\t\t task tells the user to click something in the app underneath, and an\n\t\t interactive backdrop would swallow exactly that click, leaving only\n\t\t Back/Finish usable. The trade-off is deliberate: a passthrough dim\n\t\t cannot be clicked to dismiss the tour, so on such a step the exits\n\t\t are ESC, the coachmark's close button and Back/Finish — a click on\n\t\t the greyed-out area reaches the app, which is the point. A centered\n\t\t step spotlights nothing, so its backdrop stays interactive and\n\t\t dismisses the tour. -->\n\t\t<!-- Quiet: an app dialog is open, or the step's target belongs to\n\t\t another page. No dim and no cutout, so the overlay never sits on\n\t\t top of the dialog and never invites a click on the wrong page.\n\t\t Only the docked coachmark stays. -->\n\t\t<template v-if=\"quiet\" />\n\t\t<template v-else-if=\"isCentered || !rect\">\n\t\t\t<div v-if=\"isCentered\"\n\t\t\t\tclass=\"cn-walkthrough__dim cn-walkthrough__dim--full\"\n\t\t\t\t@click.self=\"onBackdrop\" />\n\t\t\t<!-- No dismiss handler on a passthrough dim: `pointer-events: none`\n\t\t\t means a browser never delivers the click here anyway, and\n\t\t\t leaving the listener attached would make the SAME gesture\n\t\t\t dismiss the tour in any environment that ignores\n\t\t\t pointer-events (jsdom, a forced-colors mode) while reaching the\n\t\t\t app in a browser. One behaviour, not two. -->\n\t\t\t<div v-else\n\t\t\t\tclass=\"cn-walkthrough__dim cn-walkthrough__dim--full cn-walkthrough__dim--passthrough\"\n\t\t\t\taria-hidden=\"true\" />\n\t\t</template>\n\t\t<template v-else>\n\t\t\t<div class=\"cn-walkthrough__dim\" :style=\"strip.top\" @click.self=\"onBackdrop\" />\n\t\t\t<div class=\"cn-walkthrough__dim\" :style=\"strip.bottom\" @click.self=\"onBackdrop\" />\n\t\t\t<div class=\"cn-walkthrough__dim\" :style=\"strip.left\" @click.self=\"onBackdrop\" />\n\t\t\t<div class=\"cn-walkthrough__dim\" :style=\"strip.right\" @click.self=\"onBackdrop\" />\n\t\t\t<div class=\"cn-walkthrough__ring\" :style=\"ringStyle\" aria-hidden=\"true\" />\n\t\t</template>\n\n\t\t<!-- Coachmark card -->\n\t\t<div ref=\"card\"\n\t\t\tclass=\"cn-walkthrough__card\"\n\t\t\t:class=\"[`cn-walkthrough__card--${cardPlacement}`, { 'cn-walkthrough__card--docked': quiet }]\"\n\t\t\t:style=\"cardStyle\">\n\t\t\t<NcButton class=\"cn-walkthrough__close\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:aria-label=\"closeLabel\"\n\t\t\t\t:title=\"closeLabel\"\n\t\t\t\t@click=\"close\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t</NcButton>\n\t\t\t<!-- @slot coachmark Override the whole coachmark body. Scope: { step, index, total, next, back, skip, skipLabel }.\n\t\t\t The default body has no Skip control (ADR-062 gives that job to\n\t\t\t the corner close button), so `skipLabel` reaches the only place\n\t\t\t a Skip control can live: a host-supplied coachmark. -->\n\t\t\t<slot name=\"coachmark\"\n\t\t\t\t:step=\"step\"\n\t\t\t\t:index=\"index\"\n\t\t\t\t:total=\"total\"\n\t\t\t\t:next=\"advance\"\n\t\t\t\t:back=\"back\"\n\t\t\t\t:skip=\"skip\"\n\t\t\t\t:skipLabel=\"skipLabel\">\n\t\t\t\t<div class=\"cn-walkthrough__counter\">\n\t\t\t\t\t{{ index + 1 }} / {{ total }}\n\t\t\t\t</div>\n\t\t\t\t<h3 v-if=\"stepTitle\" class=\"cn-walkthrough__title\">\n\t\t\t\t\t{{ stepTitle }}\n\t\t\t\t</h3>\n\t\t\t\t<p v-if=\"stepBody\" class=\"cn-walkthrough__body\">\n\t\t\t\t\t{{ stepBody }}\n\t\t\t\t</p>\n\t\t\t\t<p v-if=\"stepTask\" class=\"cn-walkthrough__task\">\n\t\t\t\t\t<span class=\"cn-walkthrough__task-icon\" aria-hidden=\"true\">👉</span> {{ stepTask }}\n\t\t\t\t</p>\n\t\t\t\t<div class=\"cn-walkthrough__actions\">\n\t\t\t\t\t<NcButton v-if=\"!isFirst\" variant=\"secondary\" @click=\"back\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<ChevronLeft :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ backLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<span class=\"cn-walkthrough__spacer\" />\n\t\t\t\t\t<NcButton v-if=\"isHandoff\"\n\t\t\t\t\t\tref=\"firstBtn\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\talignment=\"center-reverse\"\n\t\t\t\t\t\t@click=\"doHandoff\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<ChevronRight :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ handoffLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<NcButton v-else-if=\"showNext\"\n\t\t\t\t\t\tref=\"firstBtn\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\talignment=\"center-reverse\"\n\t\t\t\t\t\t@click=\"advance\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<ChevronRight :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ isLast ? finishLabel : nextLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t</slot>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'\nimport ChevronRight from 'vue-material-design-icons/ChevronRight.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport { useWalkthrough } from '../../composables/useWalkthrough.js'\n\n/**\n * CnWalkthrough — abstract, manifest-driven product walkthrough (ADR-043).\n *\n * Renders a `manifest.walkthrough` tour as a gray dimmer with a spotlight cutout\n * around one real, interactive element, plus an auto-positioned coachmark card.\n * It never promotes the target's `z-index` — the dimmer is four strips framing\n * the target rect, so the element stays clickable in place regardless of its\n * stacking context. When a target is hidden inside a collapsed `NcAppNavigation`\n * group (children rendered but `display:none`, so unmeasurable), the engine\n * best-effort expands the group to reveal the item before spotlighting it, rather\n * than falling back to a centered coachmark. Step advancement is declarative: the component sources\n * signals (route change via `$router`, a `cn-walkthrough:object-created` window\n * event, an appearing element via `MutationObserver`, a click on the target, or a\n * delay) and feeds them to {@link useWalkthrough}, which captures route params /\n * object ids into a context bag interpolated into later steps.\n *\n * Mount it gated by `CnAppRoot` over the live shell, or standalone with an\n * explicit `tour-id`.\n *\n * ```vue\n * <CnWalkthrough :app-id=\"'pipelinq'\" :manifest=\"manifest\" :seen-version=\"seen\"\n * @complete=\"onComplete\" />\n * ```\n */\nexport default {\n\tname: 'CnWalkthrough',\n\n\tcomponents: { NcButton, Close, ChevronLeft, ChevronRight },\n\n\tprops: {\n\t\t/** The Nextcloud app id (walkthrough machine cache key). */\n\t\tappId: { type: String, required: true },\n\t\t/** The app manifest; `manifest.walkthrough` + `manifest.version` are read. */\n\t\tmanifest: { type: Object, default: null },\n\t\t/** The user's last-seen app version (drives \"what's new\" composition). */\n\t\tseenVersion: { type: String, default: '' },\n\t\t/** Force a specific tour id; when empty the auto-start tour is used. */\n\t\ttourId: { type: String, default: '' },\n\t\t/** Optional resume token `{ tourId, stepId }` (refresh / cross-app hand-off). */\n\t\tresume: { type: Object, default: null },\n\t\t/** Stacking order of the overlay. */\n\t\tzIndex: { type: Number, default: 10000 },\n\t\t/** Next button label. */\n\t\tnextLabel: { type: String, default: () => t('nextcloud-vue', 'Next') },\n\t\t/** Back button label. */\n\t\tbackLabel: { type: String, default: () => t('nextcloud-vue', 'Back') },\n\t\t/** Skip button label. */\n\t\tskipLabel: { type: String, default: () => t('nextcloud-vue', 'Skip') },\n\t\t/** Final-step button label. */\n\t\tfinishLabel: { type: String, default: () => t('nextcloud-vue', 'Finish') },\n\t\t/** Accessible label for the corner close button that ends the tour. */\n\t\tcloseLabel: { type: String, default: () => t('nextcloud-vue', 'Close tour') },\n\t\t/** Optional translate function applied to step title/body/task i18n keys. */\n\t\ttranslate: { type: Function, default: null },\n\t},\n\n\temits: ['complete', 'dismiss', 'pause', 'progress', 'step-change', 'advance', 'handoff'],\n\n\tsetup(props) {\n\t\tconst wt = useWalkthrough(props.appId, props.manifest, {\n\t\t\tseenVersion: props.seenVersion,\n\t\t\tresume: props.resume,\n\t\t\tonComplete: undefined,\n\t\t})\n\t\treturn { wt }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trect: null, // target bounding rect (viewport coords)\n\t\t\tcardPos: { top: 0, left: 0 },\n\t\t\tcardPlacement: 'bottom',\n\t\t\ttargetEl: null,\n\t\t\t// An app dialog (NcModal / NcDialog) is open over the page.\n\t\t\tappModalOpen: false,\n\t\t\t// The step's target was found on another page than the current one.\n\t\t\toffPage: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tactive() {\n\t\t\treturn this.wt.running.value && !!this.step\n\t\t},\n\n\t\tstep() {\n\t\t\treturn this.wt.currentStep.value\n\t\t},\n\n\t\tindex() {\n\t\t\treturn this.wt.activeTour.value ? this.wt.activeTour.value.steps.findIndex((s) => s.id === (this.step && this.step.id)) : 0\n\t\t},\n\n\t\ttotal() {\n\t\t\treturn this.wt.totalSteps.value\n\t\t},\n\n\t\tisFirst() {\n\t\t\treturn this.wt.isFirst.value\n\t\t},\n\n\t\tisLast() {\n\t\t\treturn this.wt.isLast.value\n\t\t},\n\n\t\t/**\n\t\t * Whether the overlay steps back: no dim, no cutout, a docked card.\n\t\t * True while an app dialog is open or the step's target is on\n\t\t * another page.\n\t\t *\n\t\t * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n\t\t * @return {boolean}\n\t\t */\n\t\tquiet() {\n\t\t\treturn this.appModalOpen || this.offPage\n\t\t},\n\n\t\tisCentered() {\n\t\t\treturn this.step && this.step.placement === 'center'\n\t\t},\n\n\t\t/**\n\t\t * Whether the Next control is shown. Hidden on an enforced action step\n\t\t * (a task + a non-manual advanceOn) unless `allowManualNext` is set.\n\t\t *\n\t\t * @return {boolean} True when Next should render.\n\t\t */\n\t\tshowNext() {\n\t\t\tif (!this.step) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst enforced = !!this.step.task && this.step.advanceOn && this.step.advanceOn.type !== 'manual'\n\t\t\treturn !enforced || this.step.allowManualNext === true\n\t\t},\n\n\t\t/**\n\t\t * Whether the active step is a cross-app hand-off (declares `handoff.url`).\n\t\t *\n\t\t * @return {boolean} True when the coachmark offers \"Continue in {app}\".\n\t\t */\n\t\tisHandoff() {\n\t\t\treturn !!(this.step && this.step.handoff && this.step.handoff.url)\n\t\t},\n\n\t\thandoffLabel() {\n\t\t\tconst app = this.step && this.step.handoff && this.step.handoff.app\n\t\t\treturn app ? t('nextcloud-vue', 'Continue in {app}', { app }) : t('nextcloud-vue', 'Continue')\n\t\t},\n\n\t\tstepTitle() {\n\t\t\treturn this.tr(this.step && this.step.title)\n\t\t},\n\n\t\tstepBody() {\n\t\t\treturn this.tr(this.step && this.step.body)\n\t\t},\n\n\t\t/**\n\t\t * The task line, through the same `translate` prop as title/body — a\n\t\t * raw `step.task` shipped English even in fully translated locales.\n\t\t *\n\t\t * @return {string} The translated task, or `''` when the step has none.\n\t\t */\n\t\tstepTask() {\n\t\t\treturn this.tr(this.step && this.step.task)\n\t\t},\n\n\t\tdialogLabel() {\n\t\t\treturn this.stepTitle || t('nextcloud-vue', 'Walkthrough')\n\t\t},\n\n\t\tliveText() {\n\t\t\tif (!this.step) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Step {n} of {total}', { n: this.index + 1, total: this.total }) + ': ' + (this.stepTitle || '')\n\t\t},\n\n\t\tringStyle() {\n\t\t\tif (!this.rect) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst pad = 6\n\t\t\treturn {\n\t\t\t\ttop: (this.rect.top - pad) + 'px',\n\t\t\t\tleft: (this.rect.left - pad) + 'px',\n\t\t\t\twidth: (this.rect.width + pad * 2) + 'px',\n\t\t\t\theight: (this.rect.height + pad * 2) + 'px',\n\t\t\t}\n\t\t},\n\n\t\tstrip() {\n\t\t\tconst r = this.rect\n\t\t\tif (!r) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst pad = 6\n\t\t\tconst top = Math.max(0, r.top - pad)\n\t\t\tconst bottom = r.top + r.height + pad\n\t\t\tconst left = Math.max(0, r.left - pad)\n\t\t\tconst right = r.left + r.width + pad\n\t\t\treturn {\n\t\t\t\ttop: { top: 0, left: 0, width: '100%', height: top + 'px' },\n\t\t\t\tbottom: { top: bottom + 'px', left: 0, width: '100%', bottom: 0 },\n\t\t\t\tleft: { top: top + 'px', left: 0, width: left + 'px', height: (bottom - top) + 'px' },\n\t\t\t\tright: { top: top + 'px', left: right + 'px', right: 0, height: (bottom - top) + 'px' },\n\t\t\t}\n\t\t},\n\n\t\tcardStyle() {\n\t\t\tif (this.quiet) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tif (this.isCentered || !this.rect) {\n\t\t\t\treturn { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }\n\t\t\t}\n\t\t\treturn { top: this.cardPos.top + 'px', left: this.cardPos.left + 'px' }\n\t\t},\n\t},\n\n\twatch: {\n\t\tstep(newStep, oldStep) {\n\t\t\tif (!newStep) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!oldStep || newStep.id !== oldStep.id) {\n\t\t\t\t/**\n\t\t\t\t * @event step-change Emitted when the active step changes.\n\t\t\t\t * @type {{ stepId: string, index: number }}\n\t\t\t\t */\n\t\t\t\tthis.$emit('step-change', { stepId: newStep.id, index: this.index })\n\t\t\t\t/**\n\t\t\t\t * @event progress Emitted on every step change so the host can remember where the user is.\n\t\t\t\t * @type {{ tourId: string, stepId: string, index: number }}\n\t\t\t\t */\n\t\t\t\tthis.$emit('progress', {\n\t\t\t\t\ttourId: (this.wt.activeTour.value && this.wt.activeTour.value.id) || '',\n\t\t\t\t\tstepId: newStep.id,\n\t\t\t\t\tindex: this.index,\n\t\t\t\t})\n\t\t\t\t// A new step finds its own page again.\n\t\t\t\tthis._homeRoute = null\n\t\t\t\tthis.offPage = false\n\t\t\t\tthis.$nextTick(() => this.locateTarget())\n\t\t\t}\n\t\t},\n\n\t\tactive(now) {\n\t\t\tif (now) {\n\t\t\t\tthis.$nextTick(() => this.locateTarget())\n\t\t\t} else {\n\t\t\t\tthis.teardownStep()\n\t\t\t}\n\t\t},\n\t},\n\n\t/**\n\t * Initialise non-reactive instance state (event handler refs, DOM observer\n\t * handles). These use the `_` prefix convention and are set here rather than\n\t * in `data()` to avoid Vue's reserved-key warning for `_`-prefixed fields.\n\t *\n\t * @spec openspec/changes/cn-walkthrough-engine/specs/cn-walkthrough/spec.md\n\t */\n\tcreated() {\n\t\t// Non-reactive instance state: event handlers and DOM watchers.\n\t\t// Not in data() to avoid triggering Vue's reserved-key warning (_prefix).\n\t\tthis._revealAttempted = false\n\t\tthis._observer = null\n\t\tthis._resizeObs = null\n\t\tthis._delayTimer = null\n\t\tthis._onScroll = null\n\t\tthis._onKey = null\n\t\tthis._onObjectCreated = null\n\t\tthis._routeUnhook = null\n\t\t// Layout watchers for the ACTIVE step's target (see trackTargetLayout).\n\t\tthis._layoutObs = null\n\t\tthis._bodyResizeObs = null\n\t\tthis._remeasureRaf = null\n\t\t// A qualifying auto-start tour whose first-step page is NOT the current\n\t\t// route — held here until the user navigates to that page (see hookRouter).\n\t\tthis._pendingAutoTour = null\n\t\t// The route the active step's target was first found on (see locateTarget).\n\t\tthis._homeRoute = null\n\t\tthis._modalObs = null\n\t},\n\n\tmounted() {\n\t\t// Auto-start a qualifying tour unless one is already running.\n\t\tthis.maybeAutoStart()\n\t\tthis._onScroll = () => this.computeRect()\n\t\twindow.addEventListener('scroll', this._onScroll, true)\n\t\twindow.addEventListener('resize', this._onScroll)\n\t\tthis._onKey = (e) => {\n\t\t\tif (e.key !== 'Escape' || !this.active) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// ESC while an app dialog is open belongs to that dialog. It used\n\t\t\t// to dismiss the tour too, so closing a create form ended it.\n\t\t\tif (this.isAppModalOpen()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.onBackdrop()\n\t\t}\n\t\twindow.addEventListener('keydown', this._onKey)\n\t\tthis._onObjectCreated = (e) => {\n\t\t\tconst d = (e && e.detail) || {}\n\t\t\tthis.wt.notify({\n\t\t\t\tkind: 'object-created',\n\t\t\t\tregister: d.register,\n\t\t\t\tschema: d.schema,\n\t\t\t\tobject: d.object && typeof d.object === 'object' ? d.object : d,\n\t\t\t})\n\t\t}\n\t\tthis.watchAppModals()\n\t\twindow.addEventListener('cn-walkthrough:object-created', this._onObjectCreated)\n\t\tthis.hookRouter()\n\t\tif (this.active) {\n\t\t\tthis.$nextTick(() => this.locateTarget())\n\t\t}\n\t},\n\n\tbeforeUnmount() {\n\t\twindow.removeEventListener('scroll', this._onScroll, true)\n\t\twindow.removeEventListener('resize', this._onScroll)\n\t\twindow.removeEventListener('keydown', this._onKey)\n\t\twindow.removeEventListener('cn-walkthrough:object-created', this._onObjectCreated)\n\t\tif (this._modalObs) {\n\t\t\tthis._modalObs.disconnect()\n\t\t\tthis._modalObs = null\n\t\t}\n\t\tif (typeof this._routeUnhook === 'function') {\n\t\t\tthis._routeUnhook()\n\t\t}\n\t\tthis.teardownStep()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether an app dialog (NcModal / NcDialog, or any other modal\n\t\t * dialog that is not this overlay) is open.\n\t\t *\n\t\t * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n\t\t * @return {boolean}\n\t\t */\n\t\tisAppModalOpen() {\n\t\t\tif (typeof document === 'undefined') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst own = this.$el\n\t\t\tconst nodes = document.querySelectorAll('.modal-mask, [role=\"dialog\"][aria-modal=\"true\"]')\n\t\t\tfor (const node of nodes) {\n\t\t\t\tif (own && typeof own.contains === 'function' && (own === node || own.contains(node))) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\t/**\n\t\t * Follow app dialogs opening and closing, so the overlay steps back\n\t\t * while one is open and comes back after.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\twatchAppModals() {\n\t\t\tconst update = () => {\n\t\t\t\tconst open = this.isAppModalOpen()\n\t\t\t\tif (open !== this.appModalOpen) {\n\t\t\t\t\tthis.appModalOpen = open\n\t\t\t\t\tif (!open && this.active) {\n\t\t\t\t\t\tthis.$nextTick(() => this.locateTarget())\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tupdate()\n\t\t\tif (typeof window !== 'undefined' && window.MutationObserver) {\n\t\t\t\tthis._modalObs = new MutationObserver(update)\n\t\t\t\tthis._modalObs.observe(document.body, { childList: true, subtree: true })\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Translate an i18n key/text via the `translate` prop when given.\n\t\t *\n\t\t * @param {string} value The key or literal.\n\t\t * @return {string} The resolved string.\n\t\t */\n\t\ttr(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn typeof this.translate === 'function' ? this.translate(value) : value\n\t\t},\n\n\t\t/**\n\t\t * Hook `$router.afterEach` (when a router is present) to feed route signals.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\thookRouter() {\n\t\t\tconst router = this.$router\n\t\t\tif (!router || typeof router.afterEach !== 'function') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis._routeUnhook = router.afterEach((to) => {\n\t\t\t\tthis.wt.notify({ kind: 'route', route: to.name, params: to.params || {} })\n\t\t\t\t// A first-visit tour deferred because the user deep-linked onto a\n\t\t\t\t// different page starts once they reach its first-step page.\n\t\t\t\tif (this._pendingAutoTour && !this.wt.running.value\n\t\t\t\t\t&& this.routeMatchesTour(this._pendingAutoTour, to.name)) {\n\t\t\t\t\tconst tour = this._pendingAutoTour\n\t\t\t\t\tthis._pendingAutoTour = null\n\t\t\t\t\tthis.wt.start(tour.id)\n\t\t\t\t}\n\t\t\t\tthis.$nextTick(() => this.locateTarget())\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Start a qualifying auto-start tour — but a `first-visit` tour whose\n\t\t * first step SPOTLIGHTS something on a specific page only opens when the\n\t\t * user is ON that page. A centered welcome step spotlights nothing and\n\t\t * therefore starts anywhere. Deep-linking onto an unrelated route (e.g. a shared detail-page\n\t\t * URL) defers the tour via `_pendingAutoTour` instead of popping it over\n\t\t * the wrong screen (ADR-062). A forced `tourId` always starts; a tour\n\t\t * whose first step is not page-anchored keeps the prior any-route\n\t\t * behaviour; without a router there is nothing to match, so it starts.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tmaybeAutoStart() {\n\t\t\tif (this.wt.running.value) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.tourId) {\n\t\t\t\tthis.wt.start(this.tourId)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst auto = this.wt.autoStartTour.value\n\t\t\tif (!auto) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!this.$router) {\n\t\t\t\tthis.wt.start(auto.id)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst routeName = this.$route && this.$route.name\n\t\t\tif (this.routeMatchesTour(auto, routeName)) {\n\t\t\t\tthis.wt.start(auto.id)\n\t\t\t} else {\n\t\t\t\tthis._pendingAutoTour = auto\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The route name a tour's FIRST step is anchored to, or null when the\n\t\t * first step is not page-anchored (a centered welcome step, or a\n\t\t * non-page target). Used to gate auto-start to the right screen.\n\t\t *\n\t\t * A `placement: \"center\"` first step returns NULL however it is\n\t\t * anchored. This is the whole point: a centered step spotlights\n\t\t * nothing — it is a welcome card floating in the middle of the screen —\n\t\t * so there is no element for the user to be \"on the right page\" for,\n\t\t * and gating its auto-open on a route only decides whether the tour\n\t\t * opens at all.\n\t\t *\n\t\t * That is what it did. This docblock already CLAIMED a centered welcome\n\t\t * step returned null; the code never checked `placement`, so any app\n\t\t * whose welcome step happened to carry a page/nav anchor other than its\n\t\t * landing route parked the tour in `_pendingAutoTour` and never opened\n\t\t * it — no error, no log, just no walkthrough. Measured on one instance\n\t\t * with an empty seen-state: opencatalogi (welcome anchored to\n\t\t * `Catalogs`) and pipelinq (anchored to `Products`) both showed nothing\n\t\t * at `#/` and the full tour at the anchored route, while dossiq\n\t\t * (anchored to its landing `Dashboard`) worked — the control that shows\n\t\t * the anchor, not the tour, was the variable. 19 of 20 fleet manifests\n\t\t * declare a walkthrough, so this is a class, not two instances.\n\t\t *\n\t\t * A step that genuinely spotlights an element keeps the ADR-062\n\t\t * behaviour, which is what it was built for: it still waits for its\n\t\t * page rather than popping over a deep-linked detail screen.\n\t\t *\n\t\t * @param {object} tour The tour definition.\n\t\t * @return {string|null} The first-step page/nav route name, or null.\n\t\t */\n\t\tfirstStepPage(tour) {\n\t\t\tconst steps = (tour && tour.steps) || []\n\t\t\tconst first = steps[0]\n\t\t\t// A CENTERED first step is not page-anchored, whatever it targets.\n\t\t\t//\n\t\t\t// The doc comment above has always said so; the code did not check.\n\t\t\t// `placement: 'center'` renders a card in the middle of the screen\n\t\t\t// and spotlights NOTHING — `locateTarget()` returns early on\n\t\t\t// `isCentered` and never resolves the target at all. So gating\n\t\t\t// auto-start on being on that step's route asks the user to be\n\t\t\t// somewhere the step will not point at.\n\t\t\t//\n\t\t\t// Consequence measured 2026-08-27 on one running instance, same\n\t\t\t// user, empty seen-state:\n\t\t\t//\n\t\t\t// opencatalogi welcome anchored to `Catalogs` -> `/` shows nothing,\n\t\t\t// `#/catalogi` shows \"1 / 8 Welcome to OpenCatalogi\"\n\t\t\t// pipelinq welcome anchored to `Products` -> `/` shows nothing,\n\t\t\t// `#/products` shows \"1 / 11 Welcome to Pipelinq\"\n\t\t\t// dossiq welcome anchored to `Dashboard` -> works (control)\n\t\t\t//\n\t\t\t// The tour was parked in `_pendingAutoTour` and opened only if the\n\t\t\t// user happened to navigate to that page — so the getting-started\n\t\t\t// tour never appeared on the landing page, silently, with nothing\n\t\t\t// logged. 19 of 20 fleet manifests declare a walkthrough, so this\n\t\t\t// is not two apps; it is however many anchored their welcome step\n\t\t\t// somewhere other than their landing route.\n\t\t\t//\n\t\t\t// ADR-062's intent is preserved exactly: a first step that really\n\t\t\t// does spotlight an element still gates on that element's route, so\n\t\t\t// a tour never pops over a deep-linked detail page.\n\t\t\tif (!first) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (first.placement === 'center') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst tgt = first.target\n\t\t\tif (tgt && (tgt.kind === 'page' || tgt.kind === 'nav-item') && tgt.ref) {\n\t\t\t\treturn String(tgt.ref)\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/**\n\t\t * Whether a tour may auto-open on the given route. True when the tour's\n\t\t * first step is not page-anchored — which includes every `center`-placed\n\t\t * welcome step, since it spotlights nothing (see firstStepPage) — so the\n\t\t * tour starts wherever the user lands. Otherwise the route name must\n\t\t * equal the first-step page.\n\t\t *\n\t\t * This is the ONLY consumer of firstStepPage, and both auto-start paths\n\t\t * (maybeAutoStart on mount, and the router afterEach that resumes a\n\t\t * deferred tour) go through here — so a centered step cannot be\n\t\t * swallowed by either.\n\t\t *\n\t\t * @param {object} tour The tour definition.\n\t\t * @param {string} [routeName] The current route name.\n\t\t * @return {boolean} True when the tour may open on this route.\n\t\t */\n\t\trouteMatchesTour(tour, routeName) {\n\t\t\tconst page = this.firstStepPage(tour)\n\t\t\tif (!page) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn !!routeName && String(routeName) === page\n\t\t},\n\n\t\t/**\n\t\t * Resolve, scroll to, and measure the active step's target, then install\n\t\t * the step's advance watcher.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tlocateTarget() {\n\t\t\tthis.teardownStep()\n\t\t\tif (!this.step || this.isCentered) {\n\t\t\t\tthis.rect = null\n\t\t\t\tthis.focusCard()\n\t\t\t\tthis.armDelay()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst el = this.resolveTarget(this.step)\n\t\t\t// A step's target belongs to the page it was first found on. The\n\t\t\t// same `index-add` button exists on every list page, and a step\n\t\t\t// left half-done on Products used to spotlight the Clients page's\n\t\t\t// button too, inviting a click that belonged to another step.\n\t\t\tconst kind = (this.step.target && this.step.target.kind) || ''\n\t\t\tconst routeName = this.$route ? this.$route.name : null\n\t\t\tif (el && kind !== 'page' && kind !== 'nav-item' && routeName) {\n\t\t\t\tif (this._homeRoute === null) {\n\t\t\t\t\tthis._homeRoute = routeName\n\t\t\t\t} else if (this._homeRoute !== routeName) {\n\t\t\t\t\tthis.offPage = true\n\t\t\t\t\tthis.targetEl = null\n\t\t\t\t\tthis.rect = null\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.offPage = false\n\t\t\tthis.targetEl = el\n\t\t\tif (!el) {\n\t\t\t\t// Optional step whose target is absent → skip; else wait for it.\n\t\t\t\tif (this.step.optional) {\n\t\t\t\t\tthis.wt.skip()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Genuinely not in the DOM (nothing to reveal) — wait for it to\n\t\t\t\t// appear. A target hidden by a collapsed nav group is handled by\n\t\t\t\t// computeRect()'s zero-size check below instead, since `el` is\n\t\t\t\t// resolvable there and revealTarget() can walk up from it.\n\t\t\t\tthis.observeForTarget()\n\t\t\t\tthis.rect = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tel.scrollIntoView({ block: 'center', inline: 'center' })\n\t\t\t} catch { /* jsdom */ }\n\t\t\tthis.computeRect()\n\t\t\tthis.armStep(el)\n\t\t\t// The ResizeObserver only fires when the target itself resizes. A nav\n\t\t\t// item can MOVE (siblings render/settle after mount) without resizing,\n\t\t\t// leaving a stale cutout one row off. Re-measure across the next few\n\t\t\t// frames so the spotlight tracks the settled position, and keep\n\t\t\t// tracking it afterwards for content that arrives later still.\n\t\t\tthis.scheduleSettleRemeasure()\n\t\t\tthis.trackTargetLayout()\n\t\t},\n\n\t\t/**\n\t\t * Resolve a step's `target` to a DOM element, preferring stable manifest\n\t\t * identities, then `data-walkthrough-id`/`data-testid`, then raw CSS.\n\t\t *\n\t\t * @param {object} step The active step.\n\t\t * @return {Element|null} The resolved element.\n\t\t */\n\t\tresolveTarget(step) {\n\t\t\tconst tgt = step.target || {}\n\t\t\tconst ref = tgt.ref\n\t\t\tconst q = (sel) => {\n\t\t\t\ttry {\n\t\t\t\t\treturn document.querySelector(sel)\n\t\t\t\t} catch {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst esc = (v) => (window.CSS && CSS.escape ? CSS.escape(v) : String(v).replace(/\"/g, '\\\\\"'))\n\t\t\tif (tgt.kind === 'selector' && tgt.selector) {\n\t\t\t\treturn q(tgt.selector)\n\t\t\t}\n\t\t\tif (!ref) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst byId = q(`[data-walkthrough-id=\"${esc(ref)}\"]`) || q(`[data-testid=\"${esc(ref)}\"]`)\n\t\t\tif (byId) {\n\t\t\t\treturn byId\n\t\t\t}\n\t\t\tif (tgt.kind === 'nav-item' || tgt.kind === 'page') {\n\t\t\t\treturn q(`[data-cn-route=\"${esc(ref)}\"]`) || q(`a[href$=\"#/${esc(ref)}\"]`) || q(`[data-route=\"${esc(ref)}\"]`)\n\t\t\t}\n\t\t\tif (tgt.kind === 'widget') {\n\t\t\t\treturn q(`[data-widget-key=\"${esc(ref)}\"]`) || q(`[data-widget-id=\"${esc(ref)}\"]`)\n\t\t\t}\n\t\t\tif (tgt.kind === 'action') {\n\t\t\t\treturn q(`[data-action-id=\"${esc(ref)}\"]`)\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/**\n\t\t * Expand only the collapsed nav groups that are actual ancestors of\n\t\t * `this.targetEl` (already resolved, just `display:none`'d by a\n\t\t * collapsed parent), instead of scanning the whole navigation.\n\t\t * Attempted at most once per step (`_revealAttempted`, reset in\n\t\t * teardownStep).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\trevealTarget() {\n\t\t\tif (this._revealAttempted || !this.targetEl) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis._revealAttempted = true\n\t\t\tconst click = (btn) => {\n\t\t\t\tif (btn && typeof btn.click === 'function') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tbtn.click()\n\t\t\t\t\t} catch { /* jsdom / detached */ }\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Walk every ancestor (not just .closest() jumps) since a nav group\n\t\t\t// and the Settings foldout use different container markup and can\n\t\t\t// nest either way.\n\t\t\tlet node = this.targetEl.parentElement\n\t\t\twhile (node) {\n\t\t\t\tif (node.classList.contains('app-navigation-entry--collapsible')) {\n\t\t\t\t\tclick(node.querySelector('button.icon-collapse, .app-navigation-entry__children-toggle, .app-navigation-entry__collapse'))\n\t\t\t\t} else if (node.matches?.('[data-testid=\"cn-nav-settings\"]')) {\n\t\t\t\t\t// NcAppNavigationSettings: CSS-module classes are hashed at\n\t\t\t\t\t// build time, so aria-expanded is the only stable handle on\n\t\t\t\t\t// its own toggle button.\n\t\t\t\t\tclick(node.querySelector('button[aria-expanded]'))\n\t\t\t\t}\n\t\t\t\tnode = node.parentElement\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Re-measure the target a few times after arming, to catch post-mount\n\t\t * layout settling (sibling nav items rendering, fonts, etc.) that moves\n\t\t * the target without resizing it. Cleared in teardownStep.\n\t\t *\n\t\t * Fast path only; later movement is caught by `trackTargetLayout()`.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tscheduleSettleRemeasure() {\n\t\t\tconst again = () => {\n\t\t\t\tif (this.targetEl && !this.isCentered) {\n\t\t\t\t\tthis.computeRect()\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._settleTimers = (this._settleTimers || [])\n\t\t\tthis._settleTimers.push(setTimeout(again, 100), setTimeout(again, 300), setTimeout(again, 600))\n\t\t},\n\n\t\t/**\n\t\t * Re-measure on any layout change for as long as the step is active.\n\t\t *\n\t\t * The settle timers above stop at 600ms; a target on a page whose content\n\t\t * arrives from the network keeps moving after that, leaving the cutout on\n\t\t * empty space. The MutationObserver catches inserted content, the\n\t\t * ResizeObserver catches reflows that insert no nodes.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\ttrackTargetLayout() {\n\t\t\tconst remeasure = () => {\n\t\t\t\tif (this._remeasureRaf) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst raf = typeof window.requestAnimationFrame === 'function'\n\t\t\t\t\t? window.requestAnimationFrame\n\t\t\t\t\t: (fn) => setTimeout(fn, 16)\n\t\t\t\tthis._remeasureRaf = raf(() => {\n\t\t\t\t\tthis._remeasureRaf = null\n\t\t\t\t\tif (this.targetEl && !this.isCentered) {\n\t\t\t\t\t\tthis.computeRect()\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t\tif (window.MutationObserver) {\n\t\t\t\tthis._layoutObs = new MutationObserver(remeasure)\n\t\t\t\tthis._layoutObs.observe(document.body, { childList: true, subtree: true })\n\t\t\t}\n\t\t\tif (window.ResizeObserver) {\n\t\t\t\tthis._bodyResizeObs = new ResizeObserver(remeasure)\n\t\t\t\tthis._bodyResizeObs.observe(document.body)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Measure the target into a viewport rect.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tcomputeRect() {\n\t\t\tif (!this.targetEl) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst r = this.targetEl.getBoundingClientRect()\n\t\t\t// Target present but not laid out (e.g. a nav item inside a collapsed\n\t\t\t// group, or a hidden element): fall back to an anchorless centered\n\t\t\t// coachmark — no point-sized cutout — until it becomes visible. The\n\t\t\t// ResizeObserver armed in armStep() recomputes when it gains size.\n\t\t\tif (r.width === 0 || r.height === 0) {\n\t\t\t\t// Resolved but not laid out — most often a nav item inside a\n\t\t\t\t// collapsed group. Expand the group so the ResizeObserver armed in\n\t\t\t\t// armStep() recomputes once it gains size; until then, anchorless.\n\t\t\t\tthis.revealTarget()\n\t\t\t\tthis.rect = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// The overlay (.cn-walkthrough) is position:fixed, but a transformed\n\t\t\t// ancestor (NC content area) can become its containing block, so its\n\t\t\t// top-left is not the viewport origin. Convert the viewport rect into\n\t\t\t// the overlay's coordinate space so the dim/ring/card line up with the\n\t\t\t// real element instead of sitting a header's height too low.\n\t\t\tconst host = this.$el && this.$el.getBoundingClientRect ? this.$el.getBoundingClientRect() : { top: 0, left: 0 }\n\t\t\t// Remember the overlay's own viewport offset so placeCard can clamp\n\t\t\t// the coachmark against the REAL viewport even when a transformed\n\t\t\t// ancestor makes the position:fixed origin non-zero.\n\t\t\tthis._hostOffset = { top: host.top, left: host.left }\n\t\t\tconst next = { top: r.top - host.top, left: r.left - host.left, width: r.width, height: r.height }\n\t\t\t// Bail when nothing moved: trackTargetLayout() calls this on every\n\t\t\t// layout change, and placeCard()'s focusCard() would otherwise steal\n\t\t\t// focus back from whatever the user is typing in.\n\t\t\tconst p = this.rect\n\t\t\tif (p && p.top === next.top && p.left === next.left && p.width === next.width && p.height === next.height) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.rect = next\n\t\t\tthis.$nextTick(() => this.placeCard())\n\t\t},\n\n\t\t/**\n\t\t * Position the coachmark near the target with a simple flip so it stays\n\t\t * on-screen.\n\t\t *\n\t\t * @spec openspec/changes/cn-walkthrough-engine/specs/cn-walkthrough/spec.md\n\t\t * @return {void}\n\t\t */\n\t\tplaceCard() {\n\t\t\tconst card = this.$refs.card\n\t\t\tif (!card || !this.rect) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst cw = card.offsetWidth || 320\n\t\t\tconst ch = card.offsetHeight || 160\n\t\t\tconst gap = 12\n\t\t\tconst vw = window.innerWidth\n\t\t\tconst vh = window.innerHeight\n\t\t\tconst r = this.rect\n\t\t\tlet placement = this.step.placement && this.step.placement !== 'auto' ? this.step.placement : 'bottom'\n\t\t\tlet top\n\t\t\tlet left\n\t\t\tif (placement === 'bottom' && r.top + r.height + gap + ch > vh) {\n\t\t\t\tplacement = 'top'\n\t\t\t}\n\t\t\tif (placement === 'top' && r.top - gap - ch < 0) {\n\t\t\t\tplacement = 'bottom'\n\t\t\t}\n\t\t\tif (placement === 'bottom') {\n\t\t\t\ttop = r.top + r.height + gap\n\t\t\t\tleft = r.left\n\t\t\t} else if (placement === 'top') {\n\t\t\t\ttop = r.top - gap - ch\n\t\t\t\tleft = r.left\n\t\t\t} else if (placement === 'left') {\n\t\t\t\ttop = r.top\n\t\t\t\tleft = r.left - gap - cw\n\t\t\t} else {\n\t\t\t\ttop = r.top\n\t\t\t\tleft = r.left + r.width + gap\n\t\t\t}\n\t\t\t// Clamp to the viewport. `left`/`top` live in overlay-relative space\n\t\t\t// (this.rect was host-subtracted), so the viewport bounds must be\n\t\t\t// host-subtracted too — otherwise a scrolled/transformed host would\n\t\t\t// let the card render off-screen (ADR-062: the coachmark is always\n\t\t\t// fully on-screen).\n\t\t\tconst ho = this._hostOffset || { top: 0, left: 0 }\n\t\t\tleft = Math.max(gap - ho.left, Math.min(left, vw - cw - gap - ho.left))\n\t\t\ttop = Math.max(gap - ho.top, Math.min(top, vh - ch - gap - ho.top))\n\t\t\tthis.cardPlacement = placement\n\t\t\tthis.cardPos = { top, left }\n\t\t\tthis.focusCard()\n\t\t},\n\n\t\t/**\n\t\t * Move focus to the coachmark's first control (focus management / a11y).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfocusCard() {\n\t\t\t// Never take focus from an open app dialog.\n\t\t\tif (this.appModalOpen) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tconst btn = this.$refs.firstBtn && this.$refs.firstBtn.$el\n\t\t\t\tif (btn && typeof btn.focus === 'function') {\n\t\t\t\t\tbtn.focus()\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Install the advance watcher for the active step (click / element / delay;\n\t\t * route + object-created are global listeners installed at mount).\n\t\t *\n\t\t * @param {Element} el The resolved target.\n\t\t * @return {void}\n\t\t */\n\t\tarmStep(el) {\n\t\t\tconst a = this.step.advanceOn || {}\n\t\t\tif (a.type === 'click-target' && el) {\n\t\t\t\tthis._clickHandler = () => this.wt.notify({ kind: 'click' })\n\t\t\t\tel.addEventListener('click', this._clickHandler, { once: true })\n\t\t\t}\n\t\t\t// Recompute the cutout when the target's size changes — covers a target\n\t\t\t// that starts hidden/zero-size (e.g. inside a collapsed nav group) and\n\t\t\t// becomes visible while the step is active.\n\t\t\tif (el && window.ResizeObserver) {\n\t\t\t\tthis._resizeObs = new ResizeObserver(() => this.computeRect())\n\t\t\t\tthis._resizeObs.observe(el)\n\t\t\t}\n\t\t\tthis.armDelay()\n\t\t},\n\n\t\t/**\n\t\t * Arm the delay timer for a `delay` advance.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tarmDelay() {\n\t\t\tconst a = (this.step && this.step.advanceOn) || {}\n\t\t\tif (a.type === 'delay') {\n\t\t\t\tthis._delayTimer = setTimeout(() => this.wt.notify({ kind: 'delay' }), a.ms || 1500)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Watch the DOM until an `element-appears` (or a not-yet-mounted) target\n\t\t * resolves, then re-locate.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tobserveForTarget() {\n\t\t\tthis._observer = new MutationObserver(() => {\n\t\t\t\tconst el = this.resolveTarget(this.step)\n\t\t\t\tif (el) {\n\t\t\t\t\tthis._observer.disconnect()\n\t\t\t\t\tthis._observer = null\n\t\t\t\t\tif (this.step.advanceOn && this.step.advanceOn.type === 'element-appears') {\n\t\t\t\t\t\tthis.wt.notify({ kind: 'element' })\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.locateTarget()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t\tthis._observer.observe(document.body, { childList: true, subtree: true })\n\t\t},\n\n\t\t/**\n\t\t * Tear down the current step's watchers/timers/listeners.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tteardownStep() {\n\t\t\tthis._revealAttempted = false\n\t\t\t// computeRect()'s bail-when-unchanged optimisation must not survive\n\t\t\t// into the next step, or a same-geometry target never re-runs\n\t\t\t// placeCard()/focusCard() on step change.\n\t\t\tthis.rect = null\n\t\t\tif (this._observer) {\n\t\t\t\tthis._observer.disconnect()\n\t\t\t\tthis._observer = null\n\t\t\t}\n\t\t\tif (this._resizeObs) {\n\t\t\t\tthis._resizeObs.disconnect()\n\t\t\t\tthis._resizeObs = null\n\t\t\t}\n\t\t\tif (this._layoutObs) {\n\t\t\t\tthis._layoutObs.disconnect()\n\t\t\t\tthis._layoutObs = null\n\t\t\t}\n\t\t\tif (this._bodyResizeObs) {\n\t\t\t\tthis._bodyResizeObs.disconnect()\n\t\t\t\tthis._bodyResizeObs = null\n\t\t\t}\n\t\t\tif (this._remeasureRaf) {\n\t\t\t\tconst cancel = typeof window.cancelAnimationFrame === 'function' ? window.cancelAnimationFrame : clearTimeout\n\t\t\t\tcancel(this._remeasureRaf)\n\t\t\t\tthis._remeasureRaf = null\n\t\t\t}\n\t\t\tif (this._delayTimer) {\n\t\t\t\tclearTimeout(this._delayTimer)\n\t\t\t\tthis._delayTimer = null\n\t\t\t}\n\t\t\tif (this._settleTimers) {\n\t\t\t\tthis._settleTimers.forEach(clearTimeout)\n\t\t\t\tthis._settleTimers = null\n\t\t\t}\n\t\t\tif (this.targetEl && this._clickHandler) {\n\t\t\t\tthis.targetEl.removeEventListener('click', this._clickHandler)\n\t\t\t\tthis._clickHandler = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Advance the tour one step, completing on the last step.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tadvance() {\n\t\t\t/**\n\t\t\t * @event advance Emitted when the user advances the tour.\n\t\t\t * @type {{ stepId: string }}\n\t\t\t */\n\t\t\tthis.$emit('advance', { stepId: this.step && this.step.id })\n\t\t\tconst wasLast = this.isLast\n\t\t\tthis.wt.next()\n\t\t\tif (wasLast) {\n\t\t\t\t/**\n\t\t\t\t * @event complete Emitted when the last step is passed.\n\t\t\t\t */\n\t\t\t\tthis.$emit('complete')\n\t\t\t}\n\t\t},\n\n\t\tback() {\n\t\t\tthis.wt.back()\n\t\t},\n\n\t\t/**\n\t\t * The coachmark's \"Skip\" control — ENDS the tour (it does not advance a\n\t\t * step). Marks the tour complete so the seen-version is persisted and it\n\t\t * won't auto-reopen, then emits `dismiss` + `complete` (ADR-062: Skip\n\t\t * dismisses the whole tour; Next is the only control that advances).\n\t\t * The internal per-step skip (an optional step whose target is absent)\n\t\t * still uses `wt.skip()` directly in `locateTarget`.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tskip() {\n\t\t\tthis.wt.complete()\n\t\t\t/**\n\t\t\t * @event dismiss Emitted when the user skips (ends) the tour.\n\t\t\t */\n\t\t\tthis.$emit('dismiss')\n\t\t\tthis.$emit('complete')\n\t\t},\n\n\t\t/**\n\t\t * Execute a cross-app hand-off: complete this tour locally, then navigate\n\t\t * the browser to the destination URL carrying a `cn_resume_tour` /\n\t\t * `cn_resume_step` token so the destination app's useWalkthrough resumes.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tdoHandoff() {\n\t\t\tconst h = (this.step && this.step.handoff) || {}\n\t\t\tif (!h.url) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst tour = h.tour || (this.wt.activeTour.value && this.wt.activeTour.value.id) || ''\n\t\t\tlet url = h.url + (h.url.indexOf('?') === -1 ? '?' : '&') + 'cn_resume_tour=' + encodeURIComponent(tour)\n\t\t\tif (h.step) {\n\t\t\t\turl += '&cn_resume_step=' + encodeURIComponent(h.step)\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event handoff Emitted just before navigating to a cross-app destination.\n\t\t\t * @type {{ app: string, url: string }}\n\t\t\t */\n\t\t\tthis.$emit('handoff', { app: h.app, url })\n\t\t\tthis.$emit('complete')\n\t\t\tthis.wt.complete()\n\t\t\ttry {\n\t\t\t\twindow.location.href = url\n\t\t\t} catch { /* jsdom */ }\n\t\t},\n\n\t\t/**\n\t\t * End the tour for good from the corner close button: mark it complete so\n\t\t * the seen-version is persisted and it does not auto-show again.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tclose() {\n\t\t\tthis.wt.complete()\n\t\t\tthis.$emit('complete')\n\t\t},\n\n\t\t/**\n\t\t * Pause the tour from a backdrop click or ESC. The tour hides and\n\t\t * keeps its step; only Skip (the close button) and Finish end it.\n\t\t * Pressing ESC used to dismiss it, and the host recorded that as\n\t\t * \"seen\", so the tour never came back.\n\t\t *\n\t\t * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n\t\t * @return {void}\n\t\t */\n\t\tonBackdrop() {\n\t\t\tthis.wt.pause()\n\t\t\t/**\n\t\t\t * @event pause Emitted when the user hides the tour (backdrop / ESC). The tour can continue later.\n\t\t\t */\n\t\t\tthis.$emit('pause')\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-walkthrough {\n\tposition: fixed;\n\tinset: 0;\n\tpointer-events: none;\n}\n\n.cn-walkthrough__live {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n}\n\n.cn-walkthrough__dim {\n\tposition: fixed;\n\tbackground: var(--color-modal-background, rgba(0, 0, 0, 0.5));\n\tpointer-events: auto;\n}\n\n.cn-walkthrough__dim--full {\n\tinset: 0;\n}\n\n/* Anchorless fallback for an ANCHORED step: dim everything, but do not\n intercept pointer events — the step's task requires clicking the app\n underneath (see the template comment). */\n/* Lets the step's own task through — and therefore cannot be clicked to\n dismiss; ESC, the close button and Back/Finish remain (see the template). */\n.cn-walkthrough__dim--passthrough {\n\tpointer-events: none;\n}\n\n.cn-walkthrough__ring {\n\tposition: fixed;\n\tborder-radius: var(--border-radius-large, 8px);\n\tbox-shadow: 0 0 0 2px var(--color-primary-element, #0082c9);\n\tpointer-events: none;\n}\n\n.cn-walkthrough__card {\n\tposition: fixed;\n\twidth: 320px;\n\tmax-width: calc(100vw - 24px);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbox-shadow: 0 2px 12px var(--color-box-shadow, rgba(0, 0, 0, 0.3));\n\tpadding: 16px;\n\tpointer-events: auto;\n}\n\n.cn-walkthrough__card--docked {\n\tinset-block-end: 16px;\n\tinset-inline-start: 16px;\n}\n\n.cn-walkthrough__close {\n\t/* !important beats NcButton's own `position: relative`, which would otherwise\n\t leave the close button in-flow at the card's top-left. */\n\tposition: absolute !important;\n\ttop: 8px;\n\tinset-inline-end: 8px;\n\tinset-inline-start: auto;\n\tz-index: 1;\n}\n\n.cn-walkthrough__counter {\n\tfont-size: 0.8125rem;\n\tcolor: var(--color-text-maxcontrast);\n\tmargin-bottom: 4px;\n\t/* Keep the counter/title clear of the absolutely-positioned close button. */\n\tpadding-inline-end: 32px;\n}\n\n.cn-walkthrough__title {\n\tmargin: 0 0 6px;\n\tfont-size: 1.1rem;\n}\n\n.cn-walkthrough__body {\n\tmargin: 0 0 8px;\n}\n\n.cn-walkthrough__task {\n\tmargin: 0 0 8px;\n\tpadding: 8px;\n\tbackground: var(--color-primary-element-light, var(--color-background-hover));\n\tborder-radius: var(--border-radius, 4px);\n\tfont-weight: 600;\n}\n\n.cn-walkthrough__task-icon {\n\tmargin-inline-end: 4px;\n}\n\n.cn-walkthrough__actions {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-top: 8px;\n}\n\n.cn-walkthrough__spacer {\n\tflex: 1 1 auto;\n}\n</style>\n"],"names":["_createElementBlock","_createElementVNode","_toDisplayString","_Fragment","_openBlock","_normalizeStyle","_normalizeClass","_createVNode","_createTextVNode","_createBlock"],"mappings":";;;;AAYO,EAAA,KAAA,EAAA,sBAAA;AAAA,EAAA,WAAA,EAAA,QAAA;;;;AAiCH,EAAA,KAAK,EAAC,gFAAE;AACR,EAAA,aAAW,EAAC,MAAE;;AAoCT,MAAA,UAAA,GAAA,EAAA,KAAA,EAAA,yBAAA,GAAA;;;AAGD,EAAA,KAAA,EAAA,uBAAA;;;;AAGD,EAAA,KAAA,EAAA,sBAAA;;;;AAGA,EAAA,KAAA,EAAA,sBAAA;;AAGE,MAAA,UAAA,GAAA,EAAA,KAAK,EAAA,yBAAO,GAAA;;;;;;;;UAzFT;oBAAVA;;AACA,QAAA,KAAK,EAAC,gBAAM;AACX,QAAA,KAAK,2BAAI,MAAM,CAAA,MAAA,EAAA,CAAA;AAChB,QAAA,IAAI,EAAC,QAAQ;AACb,QAAA,YAAU,EAAC,MAAM;AAChB,QAAA,YAAU,EAAE,QAAW,CAAA,WAAA;;QAExBC,kBAEM,CAAA,KAAA,EAFN,UAEM,EAAAC,eAAA;SAmBU,QAAK,CAAA,KAAA;0BAApBF,kBAAqB,CAAAG,QAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,EAAA,EAAA,EAAA,EAAA,uBAAA;AACD,YAAA,CAAA,QAAA,CAAA,UAAU,KAAK,KAAI,CAAA,IAAA;8CAa7B,CAAAA,QAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,EAAA;iBAZC,QAAU,CAAA,UAAA;oDAEO,CAAA,KAAA,EAAA;;AAD3B,sBAAA,KAAK,EAAA,+CAAgD;6BAC/C,wDAAO,QAAU,CAAA,UAAA,IAAA,QAAA,CAAA,UAAA,CAAA,GAAA,IAAA,CAAA,CAAA,EAAA,CAAA,MAAA,CAAA,CAAA,CAAA;;AAOvB,qBAAAC,SAAA,EAAA,EAAAJ,kBAAA,CAEc,OAFd,UAEc,CAAA,CAAA;;4BAEfA,kBAMU,CAAAG,QAAA,EAAA,EAAA,GAAA,EAAA,CAAA,EAAA,EAAA;gBALVF,kBAA+E,CAAA,KAAA,EAAA;AAA1E,kBAAA,KAAK,EAAC,qBAAsB;kBAAC,KAAK,EAAAI,cAAA,CAAE,QAAK,CAAA,KAAA,CAAC,GAAG,CAAA;AAAG,kBAAA,OAAK,wDAAO,QAAU,CAAA,UAAA,IAAA,QAAA,CAAA,UAAA,CAAA,GAAA,IAAA,CAAA,CAAA,EAAA,CAAA,MAAA,CAAA,CAAA,CAAA;;gBAC3EJ,kBAAgF,CAAA,KAAA,EAAA;AAA3E,kBAAA,KAAK,EAAC;kBAAuB,KAAK,EAAAI,cAAA,CAAE,QAAK,CAAA,KAAA,CAAC,MAAO,CAAA;AAAE,kBAAA,OAAK,wDAAO;;gBACpEJ,kBAAyE,CAAA,KAAA,EAAA;AAApE,kBAAA,KAAK,EAAC,qBAAqB;kBAAE,KAAK,EAAAI,cAAA,CAAE,QAAK,CAAA,KAAA,CAAC,IAAI,CAAA;AAAG,kBAAA,OAAK,wDAAO,QAAO,CAAA,UAAA,IAAA,QAAA,CAAA,UAAA,CAAA,GAAA,IAAA,CAAA,CAAA,EAAA,CAAA,MAAA,CAAA,CAAA,CAAA;;gBACzEJ,kBAAS,CAAA,KAAA,EAAA;AAAJ,kBAAA,KAAI,EAAA,qBAAA;kBAAA,KAAA,EAAAI,cAAA,CAAA,QAAA,CAAA,KAAA,CAAA,KAAA,CAAA;AAAA,kBAAA,OAAA,wDAAA,QAAA,CAAA,UAAA,IAAA,QAAA,CAAA,UAAA,CAAA,GAAA,IAAA,CAAA,CAAA,EAAA,CAAA,MAAA,CAAA,CAAA,CAAA;;;;;;;;QAKTJ;AAAI,UAAA,GAAG,EAAC,MAAM;AACd,UAAA,KAAK,EAACK,cAAA,CAAA,CAAA,sBAAY,EACgB,CAAA,CAAA,sBAAA,EAAA,KAAA,CAAA,aAAI,sCAAA,QAAA,CAAA,KAAA,EAAA,CAAA,CAAA,CAAA;AACrC,UAAA,KAAK,iBAAE,QAAS,CAAA,SAAA,CAAA;;qBASN,CAAA,mBAAA,EAAA;AARD,YAAA,KAAK,EAAC,uBAAQ;AACvB,YAAA,OAAO,EAAC,UAAU;AACjB,YAAA,YAAU,EAAE,MAAC,CAAA,UAAA;AACb,YAAA,KAAK,EAAE,MAAO,CAAA,UAAA;AACd,YAAA,OAAK,EAAE,QAAK,CAAA,KAAA;;AACH,YAAA,IAAA,UACT,MAAQ;cAARC,WAAQ,CAAA,gBAAA,EAAA,EAAA,IAAA,EAAA,EAAA,EAAA,CAAA;;;;oBAwDP,CAAA,IAAA,CAAA,MAAA,EAAA,WAAA,EAAA;AAhDD,YAAA,IAAI,EAAE,QAAI,CAAA,IAAA;AACV,YAAA,KAAK,EAAE,QAAK,CAAA,KAAA;AACZ,YAAA,KAAK,EAAE,QAAG,CAAA,KAAA;AACV,YAAA,IAAI,EAAE,QAAI,CAAA,OAAA;AACV,YAAA,IAAI,EAAE,QAAI,CAAA,IAAA;AACV,YAAA,IAAI,EAAE,QAAI,CAAA,IAAA;AACV,YAAA,SAAS,EAAE,MAAU,CAAA,SAAA;mBA0CpB;AAzCF,YAAAN,kBAAA,CAEM,OAFN,UAEM,EAAAC,eAAA,CADF,cAAK,GAAO,CAAA,CAAA,GAAA,KAAG,mBAAG,QAAK,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;aAEvB,QAAA,CAAA,SAAA;8BAAJF,kBAEK,CAAA,IAAA,EAFL,UAEK,EAAAE,eAAA,CADD,QAAS,CAAA,SAAA,CAAA,EAAA,CAAA,YAAA;;aAEV,QAAA,CAAA,QAAA;8BAAHF,kBAEI,CAAA,GAAA,EAFJ,UAEI,EAAAE,eAAA,CADA,QAAQ,CAAA,QAAA,CAAA,EAAA,CAAA,YAAA;;aAET,QAAA,CAAA,QAAA;AAAH,iBAAAE,SAAA,EAAA,EAAAJ,kBAAA,CAEI,KAFJ,UAEI,EAAA;4CADHC,kBAAmC,CAAA,MAAA,EAAA;AAA7B,oBAAA,KAAK,EAAC,2BAAuB;AAAA,oBAAA,aAAA,EAAA,MAAA;qBAAA,IAAA,EAAA,CAAA,CAAA,cAAA,CAAA;AAAA,kBAAAO,eAAA,CAAA,GAAA,mBAAA,QAAA,CAAA,QAAA,CAAA,EAAA,CAAA,YAAA;;;AAElC,YAAAP,kBAAA,CA4BC,OA5BD,UA4BC,EAAA;gBA3Be,QAAO,CAAA,OAAA;2CAKb,CAAA,mBAAA,EAAA;;AALe,oBAAA,OAAC,EAAA,WAAA;AAAA,oBAAA,OAAA,EAAA,QAAA,CAAA,IAAA;;AAChB,oBAAA,IAAA,UACT,MAAa;sBAAbM,WAAa,CAAA,sBAAA,EAAA,EAAA,IAAA,EAAA,EAAA,EAAA,CAAA;;qCACL,MACT;AADS,sBAAAC,eAAA,CAAA,GACT,mBAAG,MAAS,CAAA,SAAA,CAAA,EAAA,CAAA,YAAA;;;;;0DAEC,CAAA,MAAA,EAAA,EAAR,KAAK,EAAC,wBAAE,EAAA,EAAA,IAAA,EAAA,CAAA,CAAA,cAAA,CAAA;eACE,QAAC,CAAA,SAAA;gCAAhBC,WASU,CAAA,mBAAA,EAAA;;AARV,oBAAA,GAAG,EAAC,UAAU;AACd,oBAAA,OAAO,EAAC,SAAS;AACjB,oBAAA,SAAQ,EAAE,gBAAK;AACd,oBAAA,OAAK,EAAE,QAAS,CAAA,SAAA;;AACP,oBAAA,IAAA,UACT,MAAgB;sBAAhBF,WAAgB,CAAA,uBAAA,EAAA,EAAD,IAAC,EAAA,EAAA,EAAA,CAAA;;qCACR,MACT;AADS,sBAAAC,eAAA,CAAA,GACT,mBAAG,QAAY,CAAA,YAAA,CAAA,EAAA,CAAA,YAAA;;;;mBAEC,QAAA,CAAA,QAAA;kCAAhBC,WASG,CAAA,mBAAA,EAAA;;AARH,sBAAA,GAAG,EAAC,UAAU;AACd,sBAAA,OAAO,EAAC,SAAQ;AAChB,sBAAA,SAAQ,EAAE,gBAAK;AACd,sBAAA,OAAK,EAAE,QAAO,CAAA,OAAA;;AACL,sBAAA,IAAA,UACT,MAA2B;wBAA3BF,WAA2B,CAAA,uBAAA,EAAA,EAAZ,IAAI,EAAE,EAAE,EAAA,CAAA;;uCACf,MACT;AADS,wBAAAC,eAAA,CAAA,GACT,GAAGN,eAAA,CAAA,QAAA,CAAA,MAAA,GAAA,MAAA,CAAA,WAAA,GAAA,MAAA,CAAA,SAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnWalkthrough.vue3.js","sources":["../../../../src/components/CnWalkthrough/CnWalkthrough.vue?vue&type=template&id=5691d49a&lang.js"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl> -->\n<template>\n\t<div v-if=\"active\"\n\t\tclass=\"cn-walkthrough\"\n\t\t:style=\"{ zIndex }\"\n\t\trole=\"dialog\"\n\t\taria-modal=\"true\"\n\t\t:aria-label=\"dialogLabel\">\n\t\t<!-- aria-live announcement of the current step -->\n\t\t<div class=\"cn-walkthrough__live\" aria-live=\"polite\">\n\t\t\t{{ liveText }}\n\t\t</div>\n\n\t\t<!-- Dimmer: a centered step uses one full overlay; an anchored step uses\n\t\t four strips that frame the target rect, leaving a real interactive hole.\n\t\t An ANCHORED step that lost its anchor (target present but not laid\n\t\t out, e.g. inside a closed settings foldout) falls back to the full\n\t\t overlay too — but there it must let clicks THROUGH: the step's own\n\t\t task tells the user to click something in the app underneath, and an\n\t\t interactive backdrop would swallow exactly that click, leaving only\n\t\t Back/Finish usable. The trade-off is deliberate: a passthrough dim\n\t\t cannot be clicked to dismiss the tour, so on such a step the exits\n\t\t are ESC, the coachmark's close button and Back/Finish — a click on\n\t\t the greyed-out area reaches the app, which is the point. A centered\n\t\t step spotlights nothing, so its backdrop stays interactive and\n\t\t dismisses the tour. -->\n\t\t<!-- Quiet: an app dialog is open, or the step's target belongs to\n\t\t another page. No dim and no cutout, so the overlay never sits on\n\t\t top of the dialog and never invites a click on the wrong page.\n\t\t Only the docked coachmark stays. -->\n\t\t<template v-if=\"quiet\" />\n\t\t<template v-else-if=\"isCentered || !rect\">\n\t\t\t<div v-if=\"isCentered\"\n\t\t\t\tclass=\"cn-walkthrough__dim cn-walkthrough__dim--full\"\n\t\t\t\t@click.self=\"onBackdrop\" />\n\t\t\t<!-- No dismiss handler on a passthrough dim: `pointer-events: none`\n\t\t\t means a browser never delivers the click here anyway, and\n\t\t\t leaving the listener attached would make the SAME gesture\n\t\t\t dismiss the tour in any environment that ignores\n\t\t\t pointer-events (jsdom, a forced-colors mode) while reaching the\n\t\t\t app in a browser. One behaviour, not two. -->\n\t\t\t<div v-else\n\t\t\t\tclass=\"cn-walkthrough__dim cn-walkthrough__dim--full cn-walkthrough__dim--passthrough\"\n\t\t\t\taria-hidden=\"true\" />\n\t\t</template>\n\t\t<template v-else>\n\t\t\t<div class=\"cn-walkthrough__dim\" :style=\"strip.top\" @click.self=\"onBackdrop\" />\n\t\t\t<div class=\"cn-walkthrough__dim\" :style=\"strip.bottom\" @click.self=\"onBackdrop\" />\n\t\t\t<div class=\"cn-walkthrough__dim\" :style=\"strip.left\" @click.self=\"onBackdrop\" />\n\t\t\t<div class=\"cn-walkthrough__dim\" :style=\"strip.right\" @click.self=\"onBackdrop\" />\n\t\t\t<div class=\"cn-walkthrough__ring\" :style=\"ringStyle\" aria-hidden=\"true\" />\n\t\t</template>\n\n\t\t<!-- Coachmark card -->\n\t\t<div ref=\"card\"\n\t\t\tclass=\"cn-walkthrough__card\"\n\t\t\t:class=\"[`cn-walkthrough__card--${cardPlacement}`, { 'cn-walkthrough__card--docked': quiet }]\"\n\t\t\t:style=\"cardStyle\">\n\t\t\t<NcButton class=\"cn-walkthrough__close\"\n\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t:aria-label=\"closeLabel\"\n\t\t\t\t:title=\"closeLabel\"\n\t\t\t\t@click=\"close\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t</NcButton>\n\t\t\t<!-- @slot coachmark Override the whole coachmark body. Scope: { step, index, total, next, back, skip, skipLabel }.\n\t\t\t The default body has no Skip control (ADR-062 gives that job to\n\t\t\t the corner close button), so `skipLabel` reaches the only place\n\t\t\t a Skip control can live: a host-supplied coachmark. -->\n\t\t\t<slot name=\"coachmark\"\n\t\t\t\t:step=\"step\"\n\t\t\t\t:index=\"index\"\n\t\t\t\t:total=\"total\"\n\t\t\t\t:next=\"advance\"\n\t\t\t\t:back=\"back\"\n\t\t\t\t:skip=\"skip\"\n\t\t\t\t:skipLabel=\"skipLabel\">\n\t\t\t\t<div class=\"cn-walkthrough__counter\">\n\t\t\t\t\t{{ index + 1 }} / {{ total }}\n\t\t\t\t</div>\n\t\t\t\t<h3 v-if=\"stepTitle\" class=\"cn-walkthrough__title\">\n\t\t\t\t\t{{ stepTitle }}\n\t\t\t\t</h3>\n\t\t\t\t<p v-if=\"stepBody\" class=\"cn-walkthrough__body\">\n\t\t\t\t\t{{ stepBody }}\n\t\t\t\t</p>\n\t\t\t\t<p v-if=\"stepTask\" class=\"cn-walkthrough__task\">\n\t\t\t\t\t<span class=\"cn-walkthrough__task-icon\" aria-hidden=\"true\">👉</span> {{ stepTask }}\n\t\t\t\t</p>\n\t\t\t\t<div class=\"cn-walkthrough__actions\">\n\t\t\t\t\t<NcButton v-if=\"!isFirst\" variant=\"secondary\" @click=\"back\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<ChevronLeft :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ backLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<span class=\"cn-walkthrough__spacer\" />\n\t\t\t\t\t<NcButton v-if=\"isHandoff\"\n\t\t\t\t\t\tref=\"firstBtn\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\talignment=\"center-reverse\"\n\t\t\t\t\t\t@click=\"doHandoff\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<ChevronRight :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ handoffLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<NcButton v-else-if=\"showNext\"\n\t\t\t\t\t\tref=\"firstBtn\"\n\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\talignment=\"center-reverse\"\n\t\t\t\t\t\t@click=\"advance\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<ChevronRight :size=\"20\" />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t{{ isLast ? finishLabel : nextLabel }}\n\t\t\t\t\t</NcButton>\n\t\t\t\t</div>\n\t\t\t</slot>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport ChevronLeft from 'vue-material-design-icons/ChevronLeft.vue'\nimport ChevronRight from 'vue-material-design-icons/ChevronRight.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport { useWalkthrough } from '../../composables/useWalkthrough.js'\n\n/**\n * CnWalkthrough — abstract, manifest-driven product walkthrough (ADR-043).\n *\n * Renders a `manifest.walkthrough` tour as a gray dimmer with a spotlight cutout\n * around one real, interactive element, plus an auto-positioned coachmark card.\n * It never promotes the target's `z-index` — the dimmer is four strips framing\n * the target rect, so the element stays clickable in place regardless of its\n * stacking context. When a target is hidden inside a collapsed `NcAppNavigation`\n * group (children rendered but `display:none`, so unmeasurable), the engine\n * best-effort expands the group to reveal the item before spotlighting it, rather\n * than falling back to a centered coachmark. Step advancement is declarative: the component sources\n * signals (route change via `$router`, a `cn-walkthrough:object-created` window\n * event, an appearing element via `MutationObserver`, a click on the target, or a\n * delay) and feeds them to {@link useWalkthrough}, which captures route params /\n * object ids into a context bag interpolated into later steps.\n *\n * Mount it gated by `CnAppRoot` over the live shell, or standalone with an\n * explicit `tour-id`.\n *\n * ```vue\n * <CnWalkthrough :app-id=\"'pipelinq'\" :manifest=\"manifest\" :seen-version=\"seen\"\n * @complete=\"onComplete\" />\n * ```\n */\nexport default {\n\tname: 'CnWalkthrough',\n\n\tcomponents: { NcButton, Close, ChevronLeft, ChevronRight },\n\n\tprops: {\n\t\t/** The Nextcloud app id (walkthrough machine cache key). */\n\t\tappId: { type: String, required: true },\n\t\t/** The app manifest; `manifest.walkthrough` + `manifest.version` are read. */\n\t\tmanifest: { type: Object, default: null },\n\t\t/** The user's last-seen app version (drives \"what's new\" composition). */\n\t\tseenVersion: { type: String, default: '' },\n\t\t/** Force a specific tour id; when empty the auto-start tour is used. */\n\t\ttourId: { type: String, default: '' },\n\t\t/** Optional resume token `{ tourId, stepId }` (refresh / cross-app hand-off). */\n\t\tresume: { type: Object, default: null },\n\t\t/**\n\t\t * Whether a first-visit tour may start on its own. The host turns it\n\t\t * off while the user's tour is paused, so a paused tour stays hidden\n\t\t * across page loads until the user picks \"Continue\".\n\t\t */\n\t\tautoStart: { type: Boolean, default: true },\n\t\t/** Stacking order of the overlay. */\n\t\tzIndex: { type: Number, default: 10000 },\n\t\t/** Next button label. */\n\t\tnextLabel: { type: String, default: () => t('nextcloud-vue', 'Next') },\n\t\t/** Back button label. */\n\t\tbackLabel: { type: String, default: () => t('nextcloud-vue', 'Back') },\n\t\t/** Skip button label. */\n\t\tskipLabel: { type: String, default: () => t('nextcloud-vue', 'Skip') },\n\t\t/** Final-step button label. */\n\t\tfinishLabel: { type: String, default: () => t('nextcloud-vue', 'Finish') },\n\t\t/** Accessible label for the corner close button that ends the tour. */\n\t\tcloseLabel: { type: String, default: () => t('nextcloud-vue', 'Close tour') },\n\t\t/** Optional translate function applied to step title/body/task i18n keys. */\n\t\ttranslate: { type: Function, default: null },\n\t},\n\n\temits: ['complete', 'dismiss', 'pause', 'progress', 'step-change', 'advance', 'handoff'],\n\n\tsetup(props) {\n\t\tconst wt = useWalkthrough(props.appId, props.manifest, {\n\t\t\tseenVersion: props.seenVersion,\n\t\t\tresume: props.resume,\n\t\t\tonComplete: undefined,\n\t\t})\n\t\treturn { wt }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\trect: null, // target bounding rect (viewport coords)\n\t\t\tcardPos: { top: 0, left: 0 },\n\t\t\tcardPlacement: 'bottom',\n\t\t\ttargetEl: null,\n\t\t\t// An app dialog (NcModal / NcDialog) is open over the page.\n\t\t\tappModalOpen: false,\n\t\t\t// The step's target was found on another page than the current one.\n\t\t\toffPage: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\tactive() {\n\t\t\treturn this.wt.running.value && !!this.step\n\t\t},\n\n\t\tstep() {\n\t\t\treturn this.wt.currentStep.value\n\t\t},\n\n\t\tindex() {\n\t\t\treturn this.wt.activeTour.value ? this.wt.activeTour.value.steps.findIndex((s) => s.id === (this.step && this.step.id)) : 0\n\t\t},\n\n\t\ttotal() {\n\t\t\treturn this.wt.totalSteps.value\n\t\t},\n\n\t\tisFirst() {\n\t\t\treturn this.wt.isFirst.value\n\t\t},\n\n\t\tisLast() {\n\t\t\treturn this.wt.isLast.value\n\t\t},\n\n\t\t/**\n\t\t * Whether the overlay steps back: no dim, no cutout, a docked card.\n\t\t * True while an app dialog is open or the step's target is on\n\t\t * another page.\n\t\t *\n\t\t * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n\t\t * @return {boolean}\n\t\t */\n\t\tquiet() {\n\t\t\treturn this.appModalOpen || this.offPage\n\t\t},\n\n\t\tisCentered() {\n\t\t\treturn this.step && this.step.placement === 'center'\n\t\t},\n\n\t\t/**\n\t\t * Whether the Next control is shown. Hidden on an enforced action step\n\t\t * (a task + a non-manual advanceOn) unless `allowManualNext` is set.\n\t\t *\n\t\t * @return {boolean} True when Next should render.\n\t\t */\n\t\tshowNext() {\n\t\t\tif (!this.step) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst enforced = !!this.step.task && this.step.advanceOn && this.step.advanceOn.type !== 'manual'\n\t\t\treturn !enforced || this.step.allowManualNext === true\n\t\t},\n\n\t\t/**\n\t\t * Whether the active step is a cross-app hand-off (declares `handoff.url`).\n\t\t *\n\t\t * @return {boolean} True when the coachmark offers \"Continue in {app}\".\n\t\t */\n\t\tisHandoff() {\n\t\t\treturn !!(this.step && this.step.handoff && this.step.handoff.url)\n\t\t},\n\n\t\thandoffLabel() {\n\t\t\tconst app = this.step && this.step.handoff && this.step.handoff.app\n\t\t\treturn app ? t('nextcloud-vue', 'Continue in {app}', { app }) : t('nextcloud-vue', 'Continue')\n\t\t},\n\n\t\tstepTitle() {\n\t\t\treturn this.tr(this.step && this.step.title)\n\t\t},\n\n\t\tstepBody() {\n\t\t\treturn this.tr(this.step && this.step.body)\n\t\t},\n\n\t\t/**\n\t\t * The task line, through the same `translate` prop as title/body — a\n\t\t * raw `step.task` shipped English even in fully translated locales.\n\t\t *\n\t\t * @return {string} The translated task, or `''` when the step has none.\n\t\t */\n\t\tstepTask() {\n\t\t\treturn this.tr(this.step && this.step.task)\n\t\t},\n\n\t\tdialogLabel() {\n\t\t\treturn this.stepTitle || t('nextcloud-vue', 'Walkthrough')\n\t\t},\n\n\t\tliveText() {\n\t\t\tif (!this.step) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn t('nextcloud-vue', 'Step {n} of {total}', { n: this.index + 1, total: this.total }) + ': ' + (this.stepTitle || '')\n\t\t},\n\n\t\tringStyle() {\n\t\t\tif (!this.rect) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst pad = 6\n\t\t\treturn {\n\t\t\t\ttop: (this.rect.top - pad) + 'px',\n\t\t\t\tleft: (this.rect.left - pad) + 'px',\n\t\t\t\twidth: (this.rect.width + pad * 2) + 'px',\n\t\t\t\theight: (this.rect.height + pad * 2) + 'px',\n\t\t\t}\n\t\t},\n\n\t\tstrip() {\n\t\t\tconst r = this.rect\n\t\t\tif (!r) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tconst pad = 6\n\t\t\tconst top = Math.max(0, r.top - pad)\n\t\t\tconst bottom = r.top + r.height + pad\n\t\t\tconst left = Math.max(0, r.left - pad)\n\t\t\tconst right = r.left + r.width + pad\n\t\t\treturn {\n\t\t\t\ttop: { top: 0, left: 0, width: '100%', height: top + 'px' },\n\t\t\t\tbottom: { top: bottom + 'px', left: 0, width: '100%', bottom: 0 },\n\t\t\t\tleft: { top: top + 'px', left: 0, width: left + 'px', height: (bottom - top) + 'px' },\n\t\t\t\tright: { top: top + 'px', left: right + 'px', right: 0, height: (bottom - top) + 'px' },\n\t\t\t}\n\t\t},\n\n\t\tcardStyle() {\n\t\t\tif (this.quiet) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\tif (this.isCentered || !this.rect) {\n\t\t\t\treturn { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' }\n\t\t\t}\n\t\t\treturn { top: this.cardPos.top + 'px', left: this.cardPos.left + 'px' }\n\t\t},\n\t},\n\n\twatch: {\n\t\tstep(newStep, oldStep) {\n\t\t\tif (!newStep) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!oldStep || newStep.id !== oldStep.id) {\n\t\t\t\t/**\n\t\t\t\t * @event step-change Emitted when the active step changes.\n\t\t\t\t * @type {{ stepId: string, index: number }}\n\t\t\t\t */\n\t\t\t\tthis.$emit('step-change', { stepId: newStep.id, index: this.index })\n\t\t\t\t/**\n\t\t\t\t * @event progress Emitted on every step change so the host can remember where the user is.\n\t\t\t\t * @type {{ tourId: string, stepId: string, index: number }}\n\t\t\t\t */\n\t\t\t\tthis.$emit('progress', {\n\t\t\t\t\ttourId: (this.wt.activeTour.value && this.wt.activeTour.value.id) || '',\n\t\t\t\t\tstepId: newStep.id,\n\t\t\t\t\tindex: this.index,\n\t\t\t\t})\n\t\t\t\t// A new step finds its own page again.\n\t\t\t\tthis._homeRoute = null\n\t\t\t\tthis.offPage = false\n\t\t\t\tthis.$nextTick(() => this.locateTarget())\n\t\t\t}\n\t\t},\n\n\t\tactive(now) {\n\t\t\tif (now) {\n\t\t\t\tthis.$nextTick(() => this.locateTarget())\n\t\t\t} else {\n\t\t\t\tthis.teardownStep()\n\t\t\t}\n\t\t},\n\t},\n\n\t/**\n\t * Initialise non-reactive instance state (event handler refs, DOM observer\n\t * handles). These use the `_` prefix convention and are set here rather than\n\t * in `data()` to avoid Vue's reserved-key warning for `_`-prefixed fields.\n\t *\n\t * @spec openspec/changes/cn-walkthrough-engine/specs/cn-walkthrough/spec.md\n\t */\n\tcreated() {\n\t\t// Non-reactive instance state: event handlers and DOM watchers.\n\t\t// Not in data() to avoid triggering Vue's reserved-key warning (_prefix).\n\t\tthis._revealAttempted = false\n\t\tthis._observer = null\n\t\tthis._resizeObs = null\n\t\tthis._delayTimer = null\n\t\tthis._onScroll = null\n\t\tthis._onKey = null\n\t\tthis._onObjectCreated = null\n\t\tthis._routeUnhook = null\n\t\t// Layout watchers for the ACTIVE step's target (see trackTargetLayout).\n\t\tthis._layoutObs = null\n\t\tthis._bodyResizeObs = null\n\t\tthis._remeasureRaf = null\n\t\t// A qualifying auto-start tour whose first-step page is NOT the current\n\t\t// route — held here until the user navigates to that page (see hookRouter).\n\t\tthis._pendingAutoTour = null\n\t\t// The route the active step's target was first found on (see locateTarget).\n\t\tthis._homeRoute = null\n\t\tthis._modalObs = null\n\t},\n\n\tmounted() {\n\t\t// Auto-start a qualifying tour unless one is already running.\n\t\tthis.maybeAutoStart()\n\t\tthis._onScroll = () => this.computeRect()\n\t\twindow.addEventListener('scroll', this._onScroll, true)\n\t\twindow.addEventListener('resize', this._onScroll)\n\t\tthis._onKey = (e) => {\n\t\t\tif (e.key !== 'Escape' || !this.active) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// ESC while an app dialog is open belongs to that dialog. It used\n\t\t\t// to dismiss the tour too, so closing a create form ended it.\n\t\t\tif (this.isAppModalOpen()) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.onBackdrop()\n\t\t}\n\t\twindow.addEventListener('keydown', this._onKey)\n\t\tthis._onObjectCreated = (e) => {\n\t\t\tconst d = (e && e.detail) || {}\n\t\t\tthis.wt.notify({\n\t\t\t\tkind: 'object-created',\n\t\t\t\tregister: d.register,\n\t\t\t\tschema: d.schema,\n\t\t\t\tobject: d.object && typeof d.object === 'object' ? d.object : d,\n\t\t\t})\n\t\t}\n\t\tthis.watchAppModals()\n\t\twindow.addEventListener('cn-walkthrough:object-created', this._onObjectCreated)\n\t\tthis.hookRouter()\n\t\tif (this.active) {\n\t\t\tthis.$nextTick(() => this.locateTarget())\n\t\t}\n\t},\n\n\tbeforeUnmount() {\n\t\twindow.removeEventListener('scroll', this._onScroll, true)\n\t\twindow.removeEventListener('resize', this._onScroll)\n\t\twindow.removeEventListener('keydown', this._onKey)\n\t\twindow.removeEventListener('cn-walkthrough:object-created', this._onObjectCreated)\n\t\tif (this._modalObs) {\n\t\t\tthis._modalObs.disconnect()\n\t\t\tthis._modalObs = null\n\t\t}\n\t\tif (typeof this._routeUnhook === 'function') {\n\t\t\tthis._routeUnhook()\n\t\t}\n\t\tthis.teardownStep()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Whether an app dialog (NcModal / NcDialog, or any other modal\n\t\t * dialog that is not this overlay) is open.\n\t\t *\n\t\t * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n\t\t * @return {boolean}\n\t\t */\n\t\tisAppModalOpen() {\n\t\t\tif (typeof document === 'undefined') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst own = this.$el\n\t\t\tconst nodes = document.querySelectorAll('.modal-mask, [role=\"dialog\"][aria-modal=\"true\"]')\n\t\t\tfor (const node of nodes) {\n\t\t\t\tif (own && typeof own.contains === 'function' && (own === node || own.contains(node))) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\n\t\t/**\n\t\t * Follow app dialogs opening and closing, so the overlay steps back\n\t\t * while one is open and comes back after.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\twatchAppModals() {\n\t\t\tconst update = () => {\n\t\t\t\tconst open = this.isAppModalOpen()\n\t\t\t\tif (open !== this.appModalOpen) {\n\t\t\t\t\tthis.appModalOpen = open\n\t\t\t\t\tif (!open && this.active) {\n\t\t\t\t\t\tthis.$nextTick(() => this.locateTarget())\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tupdate()\n\t\t\tif (typeof window !== 'undefined' && window.MutationObserver) {\n\t\t\t\tthis._modalObs = new MutationObserver(update)\n\t\t\t\tthis._modalObs.observe(document.body, { childList: true, subtree: true })\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Translate an i18n key/text via the `translate` prop when given.\n\t\t *\n\t\t * @param {string} value The key or literal.\n\t\t * @return {string} The resolved string.\n\t\t */\n\t\ttr(value) {\n\t\t\tif (!value) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn typeof this.translate === 'function' ? this.translate(value) : value\n\t\t},\n\n\t\t/**\n\t\t * Hook `$router.afterEach` (when a router is present) to feed route signals.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\thookRouter() {\n\t\t\tconst router = this.$router\n\t\t\tif (!router || typeof router.afterEach !== 'function') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis._routeUnhook = router.afterEach((to) => {\n\t\t\t\tthis.wt.notify({ kind: 'route', route: to.name, params: to.params || {} })\n\t\t\t\t// A first-visit tour deferred because the user deep-linked onto a\n\t\t\t\t// different page starts once they reach its first-step page.\n\t\t\t\tif (this._pendingAutoTour && !this.wt.running.value\n\t\t\t\t\t&& this.routeMatchesTour(this._pendingAutoTour, to.name)) {\n\t\t\t\t\tconst tour = this._pendingAutoTour\n\t\t\t\t\tthis._pendingAutoTour = null\n\t\t\t\t\tthis.wt.start(tour.id)\n\t\t\t\t}\n\t\t\t\tthis.$nextTick(() => this.locateTarget())\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Start a qualifying auto-start tour — but a `first-visit` tour whose\n\t\t * first step SPOTLIGHTS something on a specific page only opens when the\n\t\t * user is ON that page. A centered welcome step spotlights nothing and\n\t\t * therefore starts anywhere. Deep-linking onto an unrelated route (e.g. a shared detail-page\n\t\t * URL) defers the tour via `_pendingAutoTour` instead of popping it over\n\t\t * the wrong screen (ADR-062). A forced `tourId` always starts; a tour\n\t\t * whose first step is not page-anchored keeps the prior any-route\n\t\t * behaviour; without a router there is nothing to match, so it starts.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tmaybeAutoStart() {\n\t\t\tif (this.wt.running.value) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (this.tourId) {\n\t\t\t\tthis.wt.start(this.tourId)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!this.autoStart) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst auto = this.wt.autoStartTour.value\n\t\t\tif (!auto) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (!this.$router) {\n\t\t\t\tthis.wt.start(auto.id)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst routeName = this.$route && this.$route.name\n\t\t\tif (this.routeMatchesTour(auto, routeName)) {\n\t\t\t\tthis.wt.start(auto.id)\n\t\t\t} else {\n\t\t\t\tthis._pendingAutoTour = auto\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The route name a tour's FIRST step is anchored to, or null when the\n\t\t * first step is not page-anchored (a centered welcome step, or a\n\t\t * non-page target). Used to gate auto-start to the right screen.\n\t\t *\n\t\t * A `placement: \"center\"` first step returns NULL however it is\n\t\t * anchored. This is the whole point: a centered step spotlights\n\t\t * nothing — it is a welcome card floating in the middle of the screen —\n\t\t * so there is no element for the user to be \"on the right page\" for,\n\t\t * and gating its auto-open on a route only decides whether the tour\n\t\t * opens at all.\n\t\t *\n\t\t * That is what it did. This docblock already CLAIMED a centered welcome\n\t\t * step returned null; the code never checked `placement`, so any app\n\t\t * whose welcome step happened to carry a page/nav anchor other than its\n\t\t * landing route parked the tour in `_pendingAutoTour` and never opened\n\t\t * it — no error, no log, just no walkthrough. Measured on one instance\n\t\t * with an empty seen-state: opencatalogi (welcome anchored to\n\t\t * `Catalogs`) and pipelinq (anchored to `Products`) both showed nothing\n\t\t * at `#/` and the full tour at the anchored route, while dossiq\n\t\t * (anchored to its landing `Dashboard`) worked — the control that shows\n\t\t * the anchor, not the tour, was the variable. 19 of 20 fleet manifests\n\t\t * declare a walkthrough, so this is a class, not two instances.\n\t\t *\n\t\t * A step that genuinely spotlights an element keeps the ADR-062\n\t\t * behaviour, which is what it was built for: it still waits for its\n\t\t * page rather than popping over a deep-linked detail screen.\n\t\t *\n\t\t * @param {object} tour The tour definition.\n\t\t * @return {string|null} The first-step page/nav route name, or null.\n\t\t */\n\t\tfirstStepPage(tour) {\n\t\t\tconst steps = (tour && tour.steps) || []\n\t\t\tconst first = steps[0]\n\t\t\t// A CENTERED first step is not page-anchored, whatever it targets.\n\t\t\t//\n\t\t\t// The doc comment above has always said so; the code did not check.\n\t\t\t// `placement: 'center'` renders a card in the middle of the screen\n\t\t\t// and spotlights NOTHING — `locateTarget()` returns early on\n\t\t\t// `isCentered` and never resolves the target at all. So gating\n\t\t\t// auto-start on being on that step's route asks the user to be\n\t\t\t// somewhere the step will not point at.\n\t\t\t//\n\t\t\t// Consequence measured 2026-08-27 on one running instance, same\n\t\t\t// user, empty seen-state:\n\t\t\t//\n\t\t\t// opencatalogi welcome anchored to `Catalogs` -> `/` shows nothing,\n\t\t\t// `#/catalogi` shows \"1 / 8 Welcome to OpenCatalogi\"\n\t\t\t// pipelinq welcome anchored to `Products` -> `/` shows nothing,\n\t\t\t// `#/products` shows \"1 / 11 Welcome to Pipelinq\"\n\t\t\t// dossiq welcome anchored to `Dashboard` -> works (control)\n\t\t\t//\n\t\t\t// The tour was parked in `_pendingAutoTour` and opened only if the\n\t\t\t// user happened to navigate to that page — so the getting-started\n\t\t\t// tour never appeared on the landing page, silently, with nothing\n\t\t\t// logged. 19 of 20 fleet manifests declare a walkthrough, so this\n\t\t\t// is not two apps; it is however many anchored their welcome step\n\t\t\t// somewhere other than their landing route.\n\t\t\t//\n\t\t\t// ADR-062's intent is preserved exactly: a first step that really\n\t\t\t// does spotlight an element still gates on that element's route, so\n\t\t\t// a tour never pops over a deep-linked detail page.\n\t\t\tif (!first) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tif (first.placement === 'center') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst tgt = first.target\n\t\t\tif (tgt && (tgt.kind === 'page' || tgt.kind === 'nav-item') && tgt.ref) {\n\t\t\t\treturn String(tgt.ref)\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/**\n\t\t * Whether a tour may auto-open on the given route. True when the tour's\n\t\t * first step is not page-anchored — which includes every `center`-placed\n\t\t * welcome step, since it spotlights nothing (see firstStepPage) — so the\n\t\t * tour starts wherever the user lands. Otherwise the route name must\n\t\t * equal the first-step page.\n\t\t *\n\t\t * This is the ONLY consumer of firstStepPage, and both auto-start paths\n\t\t * (maybeAutoStart on mount, and the router afterEach that resumes a\n\t\t * deferred tour) go through here — so a centered step cannot be\n\t\t * swallowed by either.\n\t\t *\n\t\t * @param {object} tour The tour definition.\n\t\t * @param {string} [routeName] The current route name.\n\t\t * @return {boolean} True when the tour may open on this route.\n\t\t */\n\t\trouteMatchesTour(tour, routeName) {\n\t\t\tconst page = this.firstStepPage(tour)\n\t\t\tif (!page) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn !!routeName && String(routeName) === page\n\t\t},\n\n\t\t/**\n\t\t * Resolve, scroll to, and measure the active step's target, then install\n\t\t * the step's advance watcher.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tlocateTarget() {\n\t\t\tthis.teardownStep()\n\t\t\tif (!this.step || this.isCentered) {\n\t\t\t\tthis.rect = null\n\t\t\t\tthis.focusCard()\n\t\t\t\tthis.armDelay()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst el = this.resolveTarget(this.step)\n\t\t\t// A step's target belongs to the page it was first found on. The\n\t\t\t// same `index-add` button exists on every list page, and a step\n\t\t\t// left half-done on Products used to spotlight the Clients page's\n\t\t\t// button too, inviting a click that belonged to another step.\n\t\t\tconst kind = (this.step.target && this.step.target.kind) || ''\n\t\t\tconst routeName = this.$route ? this.$route.name : null\n\t\t\tif (el && kind !== 'page' && kind !== 'nav-item' && routeName) {\n\t\t\t\tif (this._homeRoute === null) {\n\t\t\t\t\tthis._homeRoute = routeName\n\t\t\t\t} else if (this._homeRoute !== routeName) {\n\t\t\t\t\tthis.offPage = true\n\t\t\t\t\tthis.targetEl = null\n\t\t\t\t\tthis.rect = null\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.offPage = false\n\t\t\tthis.targetEl = el\n\t\t\tif (!el) {\n\t\t\t\t// Optional step whose target is absent → skip; else wait for it.\n\t\t\t\tif (this.step.optional) {\n\t\t\t\t\tthis.wt.skip()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Genuinely not in the DOM (nothing to reveal) — wait for it to\n\t\t\t\t// appear. A target hidden by a collapsed nav group is handled by\n\t\t\t\t// computeRect()'s zero-size check below instead, since `el` is\n\t\t\t\t// resolvable there and revealTarget() can walk up from it.\n\t\t\t\tthis.observeForTarget()\n\t\t\t\tthis.rect = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tel.scrollIntoView({ block: 'center', inline: 'center' })\n\t\t\t} catch { /* jsdom */ }\n\t\t\tthis.computeRect()\n\t\t\tthis.armStep(el)\n\t\t\t// The ResizeObserver only fires when the target itself resizes. A nav\n\t\t\t// item can MOVE (siblings render/settle after mount) without resizing,\n\t\t\t// leaving a stale cutout one row off. Re-measure across the next few\n\t\t\t// frames so the spotlight tracks the settled position, and keep\n\t\t\t// tracking it afterwards for content that arrives later still.\n\t\t\tthis.scheduleSettleRemeasure()\n\t\t\tthis.trackTargetLayout()\n\t\t},\n\n\t\t/**\n\t\t * Resolve a step's `target` to a DOM element, preferring stable manifest\n\t\t * identities, then `data-walkthrough-id`/`data-testid`, then raw CSS.\n\t\t *\n\t\t * @param {object} step The active step.\n\t\t * @return {Element|null} The resolved element.\n\t\t */\n\t\tresolveTarget(step) {\n\t\t\tconst tgt = step.target || {}\n\t\t\tconst ref = tgt.ref\n\t\t\tconst q = (sel) => {\n\t\t\t\ttry {\n\t\t\t\t\treturn document.querySelector(sel)\n\t\t\t\t} catch {\n\t\t\t\t\treturn null\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst esc = (v) => (window.CSS && CSS.escape ? CSS.escape(v) : String(v).replace(/\"/g, '\\\\\"'))\n\t\t\tif (tgt.kind === 'selector' && tgt.selector) {\n\t\t\t\treturn q(tgt.selector)\n\t\t\t}\n\t\t\tif (!ref) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst byId = q(`[data-walkthrough-id=\"${esc(ref)}\"]`) || q(`[data-testid=\"${esc(ref)}\"]`)\n\t\t\tif (byId) {\n\t\t\t\treturn byId\n\t\t\t}\n\t\t\tif (tgt.kind === 'nav-item' || tgt.kind === 'page') {\n\t\t\t\treturn q(`[data-cn-route=\"${esc(ref)}\"]`) || q(`a[href$=\"#/${esc(ref)}\"]`) || q(`[data-route=\"${esc(ref)}\"]`)\n\t\t\t}\n\t\t\tif (tgt.kind === 'widget') {\n\t\t\t\treturn q(`[data-widget-key=\"${esc(ref)}\"]`) || q(`[data-widget-id=\"${esc(ref)}\"]`)\n\t\t\t}\n\t\t\tif (tgt.kind === 'action') {\n\t\t\t\treturn q(`[data-action-id=\"${esc(ref)}\"]`)\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\n\t\t/**\n\t\t * Expand only the collapsed nav groups that are actual ancestors of\n\t\t * `this.targetEl` (already resolved, just `display:none`'d by a\n\t\t * collapsed parent), instead of scanning the whole navigation.\n\t\t * Attempted at most once per step (`_revealAttempted`, reset in\n\t\t * teardownStep).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\trevealTarget() {\n\t\t\tif (this._revealAttempted || !this.targetEl) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis._revealAttempted = true\n\t\t\tconst click = (btn) => {\n\t\t\t\tif (btn && typeof btn.click === 'function') {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tbtn.click()\n\t\t\t\t\t} catch { /* jsdom / detached */ }\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Walk every ancestor (not just .closest() jumps) since a nav group\n\t\t\t// and the Settings foldout use different container markup and can\n\t\t\t// nest either way.\n\t\t\tlet node = this.targetEl.parentElement\n\t\t\twhile (node) {\n\t\t\t\tif (node.classList.contains('app-navigation-entry--collapsible')) {\n\t\t\t\t\tclick(node.querySelector('button.icon-collapse, .app-navigation-entry__children-toggle, .app-navigation-entry__collapse'))\n\t\t\t\t} else if (node.matches?.('[data-testid=\"cn-nav-settings\"]')) {\n\t\t\t\t\t// NcAppNavigationSettings: CSS-module classes are hashed at\n\t\t\t\t\t// build time, so aria-expanded is the only stable handle on\n\t\t\t\t\t// its own toggle button.\n\t\t\t\t\tclick(node.querySelector('button[aria-expanded]'))\n\t\t\t\t}\n\t\t\t\tnode = node.parentElement\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Re-measure the target a few times after arming, to catch post-mount\n\t\t * layout settling (sibling nav items rendering, fonts, etc.) that moves\n\t\t * the target without resizing it. Cleared in teardownStep.\n\t\t *\n\t\t * Fast path only; later movement is caught by `trackTargetLayout()`.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tscheduleSettleRemeasure() {\n\t\t\tconst again = () => {\n\t\t\t\tif (this.targetEl && !this.isCentered) {\n\t\t\t\t\tthis.computeRect()\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._settleTimers = (this._settleTimers || [])\n\t\t\tthis._settleTimers.push(setTimeout(again, 100), setTimeout(again, 300), setTimeout(again, 600))\n\t\t},\n\n\t\t/**\n\t\t * Re-measure on any layout change for as long as the step is active.\n\t\t *\n\t\t * The settle timers above stop at 600ms; a target on a page whose content\n\t\t * arrives from the network keeps moving after that, leaving the cutout on\n\t\t * empty space. The MutationObserver catches inserted content, the\n\t\t * ResizeObserver catches reflows that insert no nodes.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\ttrackTargetLayout() {\n\t\t\tconst remeasure = () => {\n\t\t\t\tif (this._remeasureRaf) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst raf = typeof window.requestAnimationFrame === 'function'\n\t\t\t\t\t? window.requestAnimationFrame\n\t\t\t\t\t: (fn) => setTimeout(fn, 16)\n\t\t\t\tthis._remeasureRaf = raf(() => {\n\t\t\t\t\tthis._remeasureRaf = null\n\t\t\t\t\tif (this.targetEl && !this.isCentered) {\n\t\t\t\t\t\tthis.computeRect()\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t\tif (window.MutationObserver) {\n\t\t\t\tthis._layoutObs = new MutationObserver(remeasure)\n\t\t\t\tthis._layoutObs.observe(document.body, { childList: true, subtree: true })\n\t\t\t}\n\t\t\tif (window.ResizeObserver) {\n\t\t\t\tthis._bodyResizeObs = new ResizeObserver(remeasure)\n\t\t\t\tthis._bodyResizeObs.observe(document.body)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Measure the target into a viewport rect.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tcomputeRect() {\n\t\t\tif (!this.targetEl) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst r = this.targetEl.getBoundingClientRect()\n\t\t\t// Target present but not laid out (e.g. a nav item inside a collapsed\n\t\t\t// group, or a hidden element): fall back to an anchorless centered\n\t\t\t// coachmark — no point-sized cutout — until it becomes visible. The\n\t\t\t// ResizeObserver armed in armStep() recomputes when it gains size.\n\t\t\tif (r.width === 0 || r.height === 0) {\n\t\t\t\t// Resolved but not laid out — most often a nav item inside a\n\t\t\t\t// collapsed group. Expand the group so the ResizeObserver armed in\n\t\t\t\t// armStep() recomputes once it gains size; until then, anchorless.\n\t\t\t\tthis.revealTarget()\n\t\t\t\tthis.rect = null\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// The overlay (.cn-walkthrough) is position:fixed, but a transformed\n\t\t\t// ancestor (NC content area) can become its containing block, so its\n\t\t\t// top-left is not the viewport origin. Convert the viewport rect into\n\t\t\t// the overlay's coordinate space so the dim/ring/card line up with the\n\t\t\t// real element instead of sitting a header's height too low.\n\t\t\tconst host = this.$el && this.$el.getBoundingClientRect ? this.$el.getBoundingClientRect() : { top: 0, left: 0 }\n\t\t\t// Remember the overlay's own viewport offset so placeCard can clamp\n\t\t\t// the coachmark against the REAL viewport even when a transformed\n\t\t\t// ancestor makes the position:fixed origin non-zero.\n\t\t\tthis._hostOffset = { top: host.top, left: host.left }\n\t\t\tconst next = { top: r.top - host.top, left: r.left - host.left, width: r.width, height: r.height }\n\t\t\t// Bail when nothing moved: trackTargetLayout() calls this on every\n\t\t\t// layout change, and placeCard()'s focusCard() would otherwise steal\n\t\t\t// focus back from whatever the user is typing in.\n\t\t\tconst p = this.rect\n\t\t\tif (p && p.top === next.top && p.left === next.left && p.width === next.width && p.height === next.height) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.rect = next\n\t\t\tthis.$nextTick(() => this.placeCard())\n\t\t},\n\n\t\t/**\n\t\t * Position the coachmark near the target with a simple flip so it stays\n\t\t * on-screen.\n\t\t *\n\t\t * @spec openspec/changes/cn-walkthrough-engine/specs/cn-walkthrough/spec.md\n\t\t * @return {void}\n\t\t */\n\t\tplaceCard() {\n\t\t\tconst card = this.$refs.card\n\t\t\tif (!card || !this.rect) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst cw = card.offsetWidth || 320\n\t\t\tconst ch = card.offsetHeight || 160\n\t\t\tconst gap = 12\n\t\t\tconst vw = window.innerWidth\n\t\t\tconst vh = window.innerHeight\n\t\t\tconst r = this.rect\n\t\t\tlet placement = this.step.placement && this.step.placement !== 'auto' ? this.step.placement : 'bottom'\n\t\t\tlet top\n\t\t\tlet left\n\t\t\tif (placement === 'bottom' && r.top + r.height + gap + ch > vh) {\n\t\t\t\tplacement = 'top'\n\t\t\t}\n\t\t\tif (placement === 'top' && r.top - gap - ch < 0) {\n\t\t\t\tplacement = 'bottom'\n\t\t\t}\n\t\t\tif (placement === 'bottom') {\n\t\t\t\ttop = r.top + r.height + gap\n\t\t\t\tleft = r.left\n\t\t\t} else if (placement === 'top') {\n\t\t\t\ttop = r.top - gap - ch\n\t\t\t\tleft = r.left\n\t\t\t} else if (placement === 'left') {\n\t\t\t\ttop = r.top\n\t\t\t\tleft = r.left - gap - cw\n\t\t\t} else {\n\t\t\t\ttop = r.top\n\t\t\t\tleft = r.left + r.width + gap\n\t\t\t}\n\t\t\t// Clamp to the viewport. `left`/`top` live in overlay-relative space\n\t\t\t// (this.rect was host-subtracted), so the viewport bounds must be\n\t\t\t// host-subtracted too — otherwise a scrolled/transformed host would\n\t\t\t// let the card render off-screen (ADR-062: the coachmark is always\n\t\t\t// fully on-screen).\n\t\t\tconst ho = this._hostOffset || { top: 0, left: 0 }\n\t\t\tleft = Math.max(gap - ho.left, Math.min(left, vw - cw - gap - ho.left))\n\t\t\ttop = Math.max(gap - ho.top, Math.min(top, vh - ch - gap - ho.top))\n\t\t\tthis.cardPlacement = placement\n\t\t\tthis.cardPos = { top, left }\n\t\t\tthis.focusCard()\n\t\t},\n\n\t\t/**\n\t\t * Move focus to the coachmark's first control (focus management / a11y).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfocusCard() {\n\t\t\t// Never take focus from an open app dialog.\n\t\t\tif (this.appModalOpen) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tconst btn = this.$refs.firstBtn && this.$refs.firstBtn.$el\n\t\t\t\tif (btn && typeof btn.focus === 'function') {\n\t\t\t\t\tbtn.focus()\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * Install the advance watcher for the active step (click / element / delay;\n\t\t * route + object-created are global listeners installed at mount).\n\t\t *\n\t\t * @param {Element} el The resolved target.\n\t\t * @return {void}\n\t\t */\n\t\tarmStep(el) {\n\t\t\tconst a = this.step.advanceOn || {}\n\t\t\tif (a.type === 'click-target' && el) {\n\t\t\t\tthis._clickHandler = () => this.wt.notify({ kind: 'click' })\n\t\t\t\tel.addEventListener('click', this._clickHandler, { once: true })\n\t\t\t}\n\t\t\t// Recompute the cutout when the target's size changes — covers a target\n\t\t\t// that starts hidden/zero-size (e.g. inside a collapsed nav group) and\n\t\t\t// becomes visible while the step is active.\n\t\t\tif (el && window.ResizeObserver) {\n\t\t\t\tthis._resizeObs = new ResizeObserver(() => this.computeRect())\n\t\t\t\tthis._resizeObs.observe(el)\n\t\t\t}\n\t\t\tthis.armDelay()\n\t\t},\n\n\t\t/**\n\t\t * Arm the delay timer for a `delay` advance.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tarmDelay() {\n\t\t\tconst a = (this.step && this.step.advanceOn) || {}\n\t\t\tif (a.type === 'delay') {\n\t\t\t\tthis._delayTimer = setTimeout(() => this.wt.notify({ kind: 'delay' }), a.ms || 1500)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Watch the DOM until an `element-appears` (or a not-yet-mounted) target\n\t\t * resolves, then re-locate.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tobserveForTarget() {\n\t\t\tthis._observer = new MutationObserver(() => {\n\t\t\t\tconst el = this.resolveTarget(this.step)\n\t\t\t\tif (el) {\n\t\t\t\t\tthis._observer.disconnect()\n\t\t\t\t\tthis._observer = null\n\t\t\t\t\tif (this.step.advanceOn && this.step.advanceOn.type === 'element-appears') {\n\t\t\t\t\t\tthis.wt.notify({ kind: 'element' })\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.locateTarget()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t\tthis._observer.observe(document.body, { childList: true, subtree: true })\n\t\t},\n\n\t\t/**\n\t\t * Tear down the current step's watchers/timers/listeners.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tteardownStep() {\n\t\t\tthis._revealAttempted = false\n\t\t\t// computeRect()'s bail-when-unchanged optimisation must not survive\n\t\t\t// into the next step, or a same-geometry target never re-runs\n\t\t\t// placeCard()/focusCard() on step change.\n\t\t\tthis.rect = null\n\t\t\tif (this._observer) {\n\t\t\t\tthis._observer.disconnect()\n\t\t\t\tthis._observer = null\n\t\t\t}\n\t\t\tif (this._resizeObs) {\n\t\t\t\tthis._resizeObs.disconnect()\n\t\t\t\tthis._resizeObs = null\n\t\t\t}\n\t\t\tif (this._layoutObs) {\n\t\t\t\tthis._layoutObs.disconnect()\n\t\t\t\tthis._layoutObs = null\n\t\t\t}\n\t\t\tif (this._bodyResizeObs) {\n\t\t\t\tthis._bodyResizeObs.disconnect()\n\t\t\t\tthis._bodyResizeObs = null\n\t\t\t}\n\t\t\tif (this._remeasureRaf) {\n\t\t\t\tconst cancel = typeof window.cancelAnimationFrame === 'function' ? window.cancelAnimationFrame : clearTimeout\n\t\t\t\tcancel(this._remeasureRaf)\n\t\t\t\tthis._remeasureRaf = null\n\t\t\t}\n\t\t\tif (this._delayTimer) {\n\t\t\t\tclearTimeout(this._delayTimer)\n\t\t\t\tthis._delayTimer = null\n\t\t\t}\n\t\t\tif (this._settleTimers) {\n\t\t\t\tthis._settleTimers.forEach(clearTimeout)\n\t\t\t\tthis._settleTimers = null\n\t\t\t}\n\t\t\tif (this.targetEl && this._clickHandler) {\n\t\t\t\tthis.targetEl.removeEventListener('click', this._clickHandler)\n\t\t\t\tthis._clickHandler = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Advance the tour one step, completing on the last step.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tadvance() {\n\t\t\t/**\n\t\t\t * @event advance Emitted when the user advances the tour.\n\t\t\t * @type {{ stepId: string }}\n\t\t\t */\n\t\t\tthis.$emit('advance', { stepId: this.step && this.step.id })\n\t\t\tconst wasLast = this.isLast\n\t\t\tthis.wt.next()\n\t\t\tif (wasLast) {\n\t\t\t\t/**\n\t\t\t\t * @event complete Emitted when the last step is passed.\n\t\t\t\t */\n\t\t\t\tthis.$emit('complete')\n\t\t\t}\n\t\t},\n\n\t\tback() {\n\t\t\tthis.wt.back()\n\t\t},\n\n\t\t/**\n\t\t * The coachmark's \"Skip\" control — ENDS the tour (it does not advance a\n\t\t * step). Marks the tour complete so the seen-version is persisted and it\n\t\t * won't auto-reopen, then emits `dismiss` + `complete` (ADR-062: Skip\n\t\t * dismisses the whole tour; Next is the only control that advances).\n\t\t * The internal per-step skip (an optional step whose target is absent)\n\t\t * still uses `wt.skip()` directly in `locateTarget`.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tskip() {\n\t\t\tthis.wt.complete()\n\t\t\t/**\n\t\t\t * @event dismiss Emitted when the user skips (ends) the tour.\n\t\t\t */\n\t\t\tthis.$emit('dismiss')\n\t\t\tthis.$emit('complete')\n\t\t},\n\n\t\t/**\n\t\t * Execute a cross-app hand-off: complete this tour locally, then navigate\n\t\t * the browser to the destination URL carrying a `cn_resume_tour` /\n\t\t * `cn_resume_step` token so the destination app's useWalkthrough resumes.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tdoHandoff() {\n\t\t\tconst h = (this.step && this.step.handoff) || {}\n\t\t\tif (!h.url) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst tour = h.tour || (this.wt.activeTour.value && this.wt.activeTour.value.id) || ''\n\t\t\tlet url = h.url + (h.url.indexOf('?') === -1 ? '?' : '&') + 'cn_resume_tour=' + encodeURIComponent(tour)\n\t\t\tif (h.step) {\n\t\t\t\turl += '&cn_resume_step=' + encodeURIComponent(h.step)\n\t\t\t}\n\t\t\t/**\n\t\t\t * @event handoff Emitted just before navigating to a cross-app destination.\n\t\t\t * @type {{ app: string, url: string }}\n\t\t\t */\n\t\t\tthis.$emit('handoff', { app: h.app, url })\n\t\t\tthis.$emit('complete')\n\t\t\tthis.wt.complete()\n\t\t\ttry {\n\t\t\t\twindow.location.href = url\n\t\t\t} catch { /* jsdom */ }\n\t\t},\n\n\t\t/**\n\t\t * The corner close button PAUSES the tour, like ESC and the dim. It\n\t\t * used to complete it, and the host then wiped the saved step, so a\n\t\t * restart began at step 1 again. Only Finish on the last step (and a\n\t\t * host-supplied Skip) completes the tour; \"Start over\" in the user\n\t\t * settings stays the way back to step 1.\n\t\t *\n\t\t * @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md\n\t\t * @return {void}\n\t\t */\n\t\tclose() {\n\t\t\tthis.onBackdrop()\n\t\t},\n\n\t\t/**\n\t\t * Pause the tour from the corner X, a backdrop click or ESC. The tour\n\t\t * hides and keeps its step; only Finish (or a host Skip) ends it.\n\t\t * Pressing ESC used to dismiss it, and the host recorded that as\n\t\t * \"seen\", so the tour never came back. The `pause` event carries\n\t\t * where the user was, so the host can keep a paused tour hidden on\n\t\t * the next page load.\n\t\t *\n\t\t * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n\t\t * @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md\n\t\t * @return {void}\n\t\t */\n\t\tonBackdrop() {\n\t\t\tconst where = {\n\t\t\t\ttourId: (this.wt.activeTour.value && this.wt.activeTour.value.id) || '',\n\t\t\t\tstepId: (this.step && this.step.id) || '',\n\t\t\t\tindex: this.index,\n\t\t\t}\n\t\t\tthis.wt.pause()\n\t\t\t/**\n\t\t\t * @event pause Emitted when the user hides the tour (X, backdrop, ESC). The tour can continue later.\n\t\t\t * @type {{ tourId: string, stepId: string, index: number }}\n\t\t\t */\n\t\t\tthis.$emit('pause', where)\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-walkthrough {\n\tposition: fixed;\n\tinset: 0;\n\tpointer-events: none;\n}\n\n.cn-walkthrough__live {\n\tposition: absolute;\n\twidth: 1px;\n\theight: 1px;\n\toverflow: hidden;\n\tclip-path: inset(50%);\n}\n\n.cn-walkthrough__dim {\n\tposition: fixed;\n\tbackground: var(--color-modal-background, rgba(0, 0, 0, 0.5));\n\tpointer-events: auto;\n}\n\n.cn-walkthrough__dim--full {\n\tinset: 0;\n}\n\n/* Anchorless fallback for an ANCHORED step: dim everything, but do not\n intercept pointer events — the step's task requires clicking the app\n underneath (see the template comment). */\n/* Lets the step's own task through — and therefore cannot be clicked to\n dismiss; ESC, the close button and Back/Finish remain (see the template). */\n.cn-walkthrough__dim--passthrough {\n\tpointer-events: none;\n}\n\n.cn-walkthrough__ring {\n\tposition: fixed;\n\tborder-radius: var(--border-radius-large, 8px);\n\tbox-shadow: 0 0 0 2px var(--color-primary-element, #0082c9);\n\tpointer-events: none;\n}\n\n.cn-walkthrough__card {\n\tposition: fixed;\n\twidth: 320px;\n\tmax-width: calc(100vw - 24px);\n\tbackground: var(--color-main-background);\n\tcolor: var(--color-main-text);\n\tborder-radius: var(--border-radius-large, 8px);\n\tbox-shadow: 0 2px 12px var(--color-box-shadow, rgba(0, 0, 0, 0.3));\n\tpadding: 16px;\n\tpointer-events: auto;\n}\n\n.cn-walkthrough__card--docked {\n\tinset-block-end: 16px;\n\tinset-inline-start: 16px;\n}\n\n.cn-walkthrough__close {\n\t/* !important beats NcButton's own `position: relative`, which would otherwise\n\t leave the close button in-flow at the card's top-left. */\n\tposition: absolute !important;\n\ttop: 8px;\n\tinset-inline-end: 8px;\n\tinset-inline-start: auto;\n\tz-index: 1;\n}\n\n.cn-walkthrough__counter {\n\tfont-size: 0.8125rem;\n\tcolor: var(--color-text-maxcontrast);\n\tmargin-bottom: 4px;\n\t/* Keep the counter/title clear of the absolutely-positioned close button. */\n\tpadding-inline-end: 32px;\n}\n\n.cn-walkthrough__title {\n\tmargin: 0 0 6px;\n\tfont-size: 1.1rem;\n}\n\n.cn-walkthrough__body {\n\tmargin: 0 0 8px;\n}\n\n.cn-walkthrough__task {\n\tmargin: 0 0 8px;\n\tpadding: 8px;\n\tbackground: var(--color-primary-element-light, var(--color-background-hover));\n\tborder-radius: var(--border-radius, 4px);\n\tfont-weight: 600;\n}\n\n.cn-walkthrough__task-icon {\n\tmargin-inline-end: 4px;\n}\n\n.cn-walkthrough__actions {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin-top: 8px;\n}\n\n.cn-walkthrough__spacer {\n\tflex: 1 1 auto;\n}\n</style>\n"],"names":["_createElementBlock","_createElementVNode","_toDisplayString","_Fragment","_openBlock","_normalizeStyle","_normalizeClass","_createVNode","_createTextVNode","_createBlock"],"mappings":"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"}
|
|
@@ -217,11 +217,13 @@ function walkthroughProgressKey(configKey) {
|
|
|
217
217
|
|
|
218
218
|
/**
|
|
219
219
|
* Normalise stored progress (an object, or the JSON string a preference
|
|
220
|
-
* holds) into `{ tourId, stepId, index, version }`,
|
|
220
|
+
* holds) into `{ tourId, stepId, index, version }`, plus `paused: true`
|
|
221
|
+
* when the user paused the tour, or null.
|
|
221
222
|
*
|
|
222
223
|
* @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md
|
|
224
|
+
* @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md
|
|
223
225
|
* @param {unknown} value The stored value.
|
|
224
|
-
* @return {{tourId: string, stepId: string, index: number, version: string}|null} The progress.
|
|
226
|
+
* @return {{tourId: string, stepId: string, index: number, version: string, paused: (boolean|undefined)}|null} The progress.
|
|
225
227
|
*/
|
|
226
228
|
function normaliseWalkthroughProgress(value) {
|
|
227
229
|
let data = value;
|
|
@@ -238,12 +240,18 @@ function normaliseWalkthroughProgress(value) {
|
|
|
238
240
|
if (!isPlainObject(data) || typeof data.tourId !== 'string' || data.tourId === '') {
|
|
239
241
|
return null
|
|
240
242
|
}
|
|
241
|
-
|
|
243
|
+
const progress = {
|
|
242
244
|
tourId: data.tourId,
|
|
243
245
|
stepId: typeof data.stepId === 'string' ? data.stepId : '',
|
|
244
246
|
index: Number.isInteger(data.index) && data.index >= 0 ? data.index : 0,
|
|
245
247
|
version: typeof data.version === 'string' ? data.version : '',
|
|
248
|
+
};
|
|
249
|
+
// A paused tour stays hidden on the next visit until the user picks
|
|
250
|
+
// "Continue". Only written when true, so older values read unchanged.
|
|
251
|
+
if (data.paused === true) {
|
|
252
|
+
progress.paused = true;
|
|
246
253
|
}
|
|
254
|
+
return progress
|
|
247
255
|
}
|
|
248
256
|
|
|
249
257
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWalkthrough.js","sources":["../../../src/composables/useWalkthrough.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\nimport axios from '@nextcloud/axios'\nimport { generateUrl } from '@nextcloud/router'\nimport { computed, ref } from 'vue'\n\n/**\n * Per-`appId` cache so all consumers of one app share a single walkthrough\n * machine + persisted progress for the page lifetime (mirrors useSetupStatus).\n */\nconst cache = new Map()\n\n/**\n * localStorage key prefix for the per-browser mirror of the last-seen version.\n * The AUTHORITATIVE store is the per-user server preference addressed by\n * `manifest.walkthrough.completionConfigKey`; this is the synchronous cache /\n * offline fallback so the tour never flashes before the GET resolves.\n */\nexport const WALKTHROUGH_SEEN_STORAGE_PREFIX = 'cn-walkthrough-seen:'\n\n/**\n * Build the per-user preferences endpoint for a walkthrough completion key.\n * Read (`GET`) and write (`PUT`) MUST address the same URL — a persist that\n * lands on a different key reads back as \"never seen\" forever.\n *\n * @param {string} appId The Nextcloud app id.\n * @param {string} configKey `manifest.walkthrough.completionConfigKey`.\n * @return {string} The generated `/apps/{appId}/api/preferences/{key}` URL.\n */\nexport function walkthroughPreferenceUrl(appId, configKey) {\n\treturn generateUrl('/apps/' + appId + '/api/preferences/' + configKey)\n}\n\n/**\n * Normalise a stored completion value into the string the version engine\n * compares against.\n *\n * Only `null` / `undefined` / `''` mean \"never seen\". Anything else counts as\n * seen — including the JS-falsy scalars `false`, `0` and `'0'`, which a plain\n * truthiness check would misread as a fresh user and re-open the tour on every\n * visit. This is the read-side counterpart of `persistWalkthroughSeenVersion`.\n *\n * @param {unknown} value The raw stored / API value.\n * @return {string} The last-seen version, or `''` when never seen.\n */\nexport function normaliseSeenVersion(value) {\n\tif (value === null || value === undefined) {\n\t\treturn ''\n\t}\n\tif (typeof value === 'string') {\n\t\treturn value\n\t}\n\treturn String(value)\n}\n\n/**\n * Resolve the injectable `localStorage` backend (null in SSR / locked-down\n * browsers, where persistence degrades to session-only).\n *\n * @param {Storage} [injected] Test-injected storage.\n * @return {Storage|null} The storage backend or null.\n */\nfunction resolveStorage(injected) {\n\tif (injected) {\n\t\treturn injected\n\t}\n\tif (typeof window === 'undefined') {\n\t\treturn null\n\t}\n\ttry {\n\t\treturn window.localStorage\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Read the per-browser mirror of the last-seen walkthrough version.\n * Synchronous, so callers can seed state before the server GET resolves.\n *\n * @param {string} appId The Nextcloud app id.\n * @param {Storage} [storage] Injectable storage backend.\n * @return {string} The last-seen version, or `''`.\n */\nexport function readLocalWalkthroughSeenVersion(appId, storage) {\n\tconst s = resolveStorage(storage)\n\tif (!s) {\n\t\treturn ''\n\t}\n\ttry {\n\t\treturn normaliseSeenVersion(s.getItem(WALKTHROUGH_SEEN_STORAGE_PREFIX + appId))\n\t} catch {\n\t\treturn ''\n\t}\n}\n\n/**\n * Write the per-browser mirror of the last-seen walkthrough version.\n *\n * @param {string} appId The Nextcloud app id.\n * @param {string} version The version to record.\n * @param {Storage} [storage] Injectable storage backend.\n * @return {void}\n */\nfunction writeLocalWalkthroughSeenVersion(appId, version, storage) {\n\tconst s = resolveStorage(storage)\n\tif (!s) {\n\t\treturn\n\t}\n\ttry {\n\t\ts.setItem(WALKTHROUGH_SEEN_STORAGE_PREFIX + appId, version)\n\t} catch {\n\t\t/* quota / private mode — persistence is best-effort */\n\t}\n}\n\n/**\n * Type guard — true for a plain (non-null, non-array) object. Used to reject\n * the SPA index HTML Nextcloud returns (with status 200) when an app does not\n * actually serve `/api/preferences/{key}`.\n *\n * @param {unknown} value Candidate.\n * @return {boolean} True when value is a plain object.\n */\nfunction isPlainObject(value) {\n\treturn value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\n/**\n * Load the user's last-seen walkthrough version (ADR-043, REQ-WALK-NV-004).\n *\n * The per-USER server preference addressed by `completionConfigKey` is\n * authoritative (cross-device); the localStorage mirror is the fallback used\n * when no key is declared, the endpoint is absent, or the request fails. A\n * `{ value: null }` response is a definitive \"never seen\" and correctly yields\n * the local mirror (usually `''`).\n *\n * @param {string} appId The Nextcloud app id.\n * @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).\n * @param {object} [options] `{ http, storage }` injection points for tests.\n * @return {Promise<string>} The last-seen version, or `''` for a fresh user.\n */\nexport async function loadWalkthroughSeenVersion(appId, configKey, options = {}) {\n\tconst local = readLocalWalkthroughSeenVersion(appId, options.storage)\n\tif (!configKey) {\n\t\treturn local\n\t}\n\tconst http = options.http || axios\n\ttry {\n\t\tconst { data } = await http.get(walkthroughPreferenceUrl(appId, configKey))\n\t\t// Only a real preferences payload counts. An HTML string (SPA fallback\n\t\t// from an app that doesn't serve the route) is NOT a \"never seen\"\n\t\t// signal — fall back to the local mirror instead.\n\t\tif (!isPlainObject(data) || !('value' in data)) {\n\t\t\treturn local\n\t\t}\n\t\tconst seen = normaliseSeenVersion(data.value)\n\t\tif (seen) {\n\t\t\twriteLocalWalkthroughSeenVersion(appId, seen, options.storage)\n\t\t\treturn seen\n\t\t}\n\t\treturn local\n\t} catch {\n\t\t// Unauthenticated / endpoint missing / offline.\n\t\treturn local\n\t}\n}\n\n/**\n * Persist the user's last-seen walkthrough version (ADR-043, REQ-WALK-NV-006).\n *\n * Writes the localStorage mirror synchronously AND `PUT`s the same\n * `completionConfigKey` preference the load path reads, so a returning user —\n * on this browser or any other — is not shown the tour again. Never throws:\n * a failed write only means the tour may re-open elsewhere.\n *\n * @param {string} appId The Nextcloud app id.\n * @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).\n * @param {string} version The app version to record as seen.\n * @param {object} [options] `{ http, storage }` injection points for tests.\n * @return {Promise<boolean>} True when the server preference was written.\n */\nexport function persistWalkthroughSeenVersion(appId, configKey, version, options = {}) {\n\tconst value = normaliseSeenVersion(version)\n\twriteLocalWalkthroughSeenVersion(appId, value, options.storage)\n\tif (!configKey) {\n\t\treturn Promise.resolve(false)\n\t}\n\tconst http = options.http || axios\n\ttry {\n\t\treturn Promise.resolve(http.put(walkthroughPreferenceUrl(appId, configKey), { value }))\n\t\t\t.then(() => true)\n\t\t\t.catch(() => false)\n\t} catch {\n\t\treturn Promise.resolve(false)\n\t}\n}\n\n/**\n * localStorage key prefix for the per-browser mirror of tour progress.\n */\nexport const WALKTHROUGH_PROGRESS_STORAGE_PREFIX = 'cn-walkthrough-progress:'\n\n/**\n * The preference key that holds tour progress, next to the completion key.\n *\n * @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).\n * @return {string} `<configKey>-progress`, or '' when no key is declared.\n */\nexport function walkthroughProgressKey(configKey) {\n\treturn configKey ? configKey + '-progress' : ''\n}\n\n/**\n * Normalise stored progress (an object, or the JSON string a preference\n * holds) into `{ tourId, stepId, index, version }`, or null.\n *\n * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n * @param {unknown} value The stored value.\n * @return {{tourId: string, stepId: string, index: number, version: string}|null} The progress.\n */\nexport function normaliseWalkthroughProgress(value) {\n\tlet data = value\n\tif (typeof data === 'string') {\n\t\tif (data === '') {\n\t\t\treturn null\n\t\t}\n\t\ttry {\n\t\t\tdata = JSON.parse(data)\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\t}\n\tif (!isPlainObject(data) || typeof data.tourId !== 'string' || data.tourId === '') {\n\t\treturn null\n\t}\n\treturn {\n\t\ttourId: data.tourId,\n\t\tstepId: typeof data.stepId === 'string' ? data.stepId : '',\n\t\tindex: Number.isInteger(data.index) && data.index >= 0 ? data.index : 0,\n\t\tversion: typeof data.version === 'string' ? data.version : '',\n\t}\n}\n\n/**\n * Read the per-browser mirror of the tour progress. Synchronous.\n *\n * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n * @param {string} appId The Nextcloud app id.\n * @param {Storage} [storage] Injectable storage backend.\n * @return {object|null} The progress, or null.\n */\nexport function readLocalWalkthroughProgress(appId, storage) {\n\tconst s = resolveStorage(storage)\n\tif (!s) {\n\t\treturn null\n\t}\n\ttry {\n\t\treturn normaliseWalkthroughProgress(s.getItem(WALKTHROUGH_PROGRESS_STORAGE_PREFIX + appId))\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Load the user's tour progress: the per-user preference\n * `<completionConfigKey>-progress` when declared, else the local mirror.\n *\n * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n * @param {string} appId The Nextcloud app id.\n * @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).\n * @param {object} [options] `{ http, storage }` injection points for tests.\n * @return {Promise<object|null>} The progress, or null.\n */\nexport async function loadWalkthroughProgress(appId, configKey, options = {}) {\n\tconst local = readLocalWalkthroughProgress(appId, options.storage)\n\tconst key = walkthroughProgressKey(configKey)\n\tif (!key) {\n\t\treturn local\n\t}\n\tconst http = options.http || axios\n\ttry {\n\t\tconst { data } = await http.get(walkthroughPreferenceUrl(appId, key))\n\t\tif (!isPlainObject(data) || !('value' in data)) {\n\t\t\treturn local\n\t\t}\n\t\treturn normaliseWalkthroughProgress(data.value) || local\n\t} catch {\n\t\treturn local\n\t}\n}\n\n/**\n * Persist the user's tour progress, or clear it with `null`. Writes the\n * local mirror and, with a completion key, the per-user preference. Never\n * throws.\n *\n * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n * @param {string} appId The Nextcloud app id.\n * @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).\n * @param {object|null} progress `{ tourId, stepId, index, version }`, or null to clear.\n * @param {object} [options] `{ http, storage }` injection points for tests.\n * @return {Promise<boolean>} True when the server preference was written.\n */\nexport function persistWalkthroughProgress(appId, configKey, progress, options = {}) {\n\tconst value = progress ? normaliseWalkthroughProgress(progress) : null\n\tconst raw = value ? JSON.stringify(value) : ''\n\tconst s = resolveStorage(options.storage)\n\tif (s) {\n\t\ttry {\n\t\t\tif (raw) {\n\t\t\t\ts.setItem(WALKTHROUGH_PROGRESS_STORAGE_PREFIX + appId, raw)\n\t\t\t} else {\n\t\t\t\ts.removeItem(WALKTHROUGH_PROGRESS_STORAGE_PREFIX + appId)\n\t\t\t}\n\t\t} catch {\n\t\t\t/* quota / private mode: best-effort */\n\t\t}\n\t}\n\tconst key = walkthroughProgressKey(configKey)\n\tif (!key) {\n\t\treturn Promise.resolve(false)\n\t}\n\tconst http = options.http || axios\n\ttry {\n\t\treturn Promise.resolve(http.put(walkthroughPreferenceUrl(appId, key), { value: raw }))\n\t\t\t.then(() => true)\n\t\t\t.catch(() => false)\n\t} catch {\n\t\treturn Promise.resolve(false)\n\t}\n}\n\n/**\n * Whether an `object-created` signal satisfies a step's `advanceOn`.\n *\n * The signal's own `register` / `schema` (the slugs the creating surface\n * knows) are compared first; an object's `@self` values are numeric ids on\n * OpenRegister and only match a step that also uses ids.\n *\n * @param {object} advanceOn The step's `advanceOn`.\n * @param {object} signal `{ register?, schema?, object? }`.\n * @return {boolean} True when it matches.\n */\nfunction objectCreatedMatches(advanceOn, signal) {\n\tconst obj = signal.object || {}\n\tconst self = obj['@self'] || {}\n\tconst same = (want, candidates) => !want || candidates\n\t\t.filter((c) => c !== null && c !== undefined && c !== '')\n\t\t.some((c) => String(c).toLowerCase() === String(want).toLowerCase())\n\treturn same(advanceOn.register, [signal.register, obj.register, self.register])\n\t\t&& same(advanceOn.schema, [signal.schema, obj.schema, self.schema])\n}\n\n/**\n * Compare two semver-ish strings. Missing / unparseable parts sort as 0.\n *\n * @param {string} a Left version.\n * @param {string} b Right version.\n * @return {number} -1 / 0 / 1.\n */\nexport function compareSemver(a, b) {\n\tconst pa = String(a || '0').split('.').map((n) => parseInt(n, 10) || 0)\n\tconst pb = String(b || '0').split('.').map((n) => parseInt(n, 10) || 0)\n\tfor (let i = 0; i < Math.max(pa.length, pb.length); i++) {\n\t\tconst d = (pa[i] || 0) - (pb[i] || 0)\n\t\tif (d !== 0) {\n\t\t\treturn d < 0 ? -1 : 1\n\t\t}\n\t}\n\treturn 0\n}\n\n/**\n * Replace `{{var}}` tokens in a string from a context bag. Unknown keys are\n * left intact (and warned in dev) so a misauthored tour degrades visibly.\n *\n * @param {string} input The template string.\n * @param {object} context The context bag.\n * @return {string} The interpolated string.\n */\nexport function interpolateTokens(input, context) {\n\tif (typeof input !== 'string' || input.indexOf('{{') === -1) {\n\t\treturn input\n\t}\n\treturn input.replace(/\\{\\{\\s*([\\w.-]+)\\s*\\}\\}/g, (m, key) => {\n\t\tif (context && Object.hasOwn(context, key) && context[key] !== null && context[key] !== undefined) {\n\t\t\treturn String(context[key])\n\t\t}\n\t\treturn m\n\t})\n}\n\n/**\n * Walkthrough engine (ADR-043). Loads `manifest.walkthrough`, composes the\n * active tour for the current user + app version, and runs a step machine whose\n * advancement is fed declarative *signals* (route change, object created,\n * element appeared, click, delay) by the rendering component — keeping this\n * composable pure and unit-testable. Captured route params / object ids land in\n * a context bag interpolated into later steps via `{{var}}`.\n *\n * Persistence is decoupled: pass `seenVersion` (the user's last-seen app version)\n * and an `onComplete(appVersion)` callback. CnAppRoot wires these to the per-user\n * `manifest.walkthrough.completionConfigKey` preference via\n * {@link loadWalkthroughSeenVersion} / {@link persistWalkthroughSeenVersion}.\n *\n * @param {string} appId The Nextcloud app id (cache key).\n * @param {object} manifest The app manifest (reads `manifest.walkthrough` + `manifest.version`).\n * @param {object} [options] `{ appVersion, seenVersion, resume, onComplete }`.\n * @return {object} The reactive machine + controls (see properties below).\n *\n * @example\n * const wt = useWalkthrough('pipelinq', manifest, { seenVersion: '1.0.0' })\n * wt.start('getting-started')\n * wt.notify({ kind: 'route', route: 'Products', params: { id: '42' } })\n */\nexport function useWalkthrough(appId, manifest, options = {}) {\n\tlet entry = cache.get(appId)\n\tif (!entry) {\n\t\tconst walkthrough = (manifest && manifest.walkthrough) || {}\n\t\tconst appVersion = options.appVersion || (manifest && manifest.version) || '0.0.0'\n\t\t// Normalised, NOT `|| ''`: a completed user whose recorded version is a\n\t\t// JS-falsy scalar (`0` / `false`) must still read as \"has seen it\".\n\t\tconst seenVersion = normaliseSeenVersion(options.seenVersion)\n\t\tconst enabled = walkthrough.enabled !== false && Array.isArray(walkthrough.tours) && walkthrough.tours.length > 0\n\n\t\tconst tours = walkthrough.tours || []\n\t\tconst activeTourId = ref(null)\n\t\tconst currentIndex = ref(0)\n\t\tconst context = ref({})\n\t\tconst running = ref(false)\n\t\t// True while the tour is hidden by ESC or a backdrop click. The tour\n\t\t// and its step stay, so it continues where it was.\n\t\tconst paused = ref(false)\n\t\t// True while a user-initiated replay is active (the \"Restart tutorial\"\n\t\t// entry). A replay shows the FULL tour regardless of the persisted\n\t\t// seen-version — otherwise a returning user, whose seenVersion already\n\t\t// covers every step's sinceVersion, would replay an empty tour.\n\t\tconst replaying = ref(false)\n\n\t\t/**\n\t\t * Compose the step set for a tour, filtered by version: a fresh user\n\t\t * (no seenVersion) gets all steps `<= appVersion`; an upgraded user gets\n\t\t * only steps newer than seenVersion and `<= appVersion`. A user-initiated\n\t\t * replay (`replaying`) ignores seenVersion and shows every step\n\t\t * `<= appVersion`.\n\t\t *\n\t\t * @param {object} tour The tour definition.\n\t\t * @return {Array} The visible steps for this user.\n\t\t */\n\t\tfunction composeSteps(tour) {\n\t\t\tconst all = Array.isArray(tour.steps) ? tour.steps : []\n\t\t\treturn all.filter((s) => {\n\t\t\t\tconst since = s.sinceVersion || '0.0.0'\n\t\t\t\tif (compareSemver(since, appVersion) > 0) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tif (!replaying.value && seenVersion && compareSemver(since, seenVersion) <= 0) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t}\n\n\t\tconst activeTour = computed(() => {\n\t\t\tif (!activeTourId.value) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst tour = tours.find((t) => t.id === activeTourId.value)\n\t\t\tif (!tour) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn { ...tour, steps: composeSteps(tour) }\n\t\t})\n\n\t\t/**\n\t\t * The tour that should auto-start for this user, or null. A\n\t\t * `version-bump` tour only qualifies when it has a non-empty step delta;\n\t\t * `first-visit` qualifies only when the user has never seen the tour.\n\t\t *\n\t\t * @return {object|null} The auto-start tour (raw definition) or null.\n\t\t */\n\t\tconst autoStartTour = computed(() => {\n\t\t\tif (!enabled) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tfor (const tour of tours) {\n\t\t\t\tif (tour.minAppVersion && compareSemver(appVersion, tour.minAppVersion) < 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst steps = composeSteps(tour)\n\t\t\t\tif (steps.length === 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (tour.trigger === 'first-visit' && !seenVersion) {\n\t\t\t\t\treturn tour\n\t\t\t\t}\n\t\t\t\tif (tour.trigger === 'version-bump' && seenVersion && compareSemver(appVersion, seenVersion) > 0) {\n\t\t\t\t\treturn tour\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn null\n\t\t})\n\n\t\tconst currentStep = computed(() => {\n\t\t\tconst t = activeTour.value\n\t\t\tif (!t) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst raw = t.steps[currentIndex.value]\n\t\t\tif (!raw) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn interpolateStep(raw, context.value)\n\t\t})\n\t\tconst totalSteps = computed(() => (activeTour.value ? activeTour.value.steps.length : 0))\n\t\tconst isFirst = computed(() => currentIndex.value === 0)\n\t\tconst isLast = computed(() => totalSteps.value > 0 && currentIndex.value === totalSteps.value - 1)\n\n\t\t/**\n\t\t * Apply `{{var}}` interpolation to a step's user-facing + targeting fields.\n\t\t *\n\t\t * @param {object} step The raw step.\n\t\t * @param {object} ctx The context bag.\n\t\t * @return {object} A shallow copy with tokens resolved.\n\t\t */\n\t\tfunction interpolateStep(step, ctx) {\n\t\t\tconst out = { ...step }\n\t\t\tfor (const k of ['title', 'body', 'task']) {\n\t\t\t\tif (out[k]) {\n\t\t\t\t\tout[k] = interpolateTokens(out[k], ctx)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (out.target) {\n\t\t\t\tout.target = { ...out.target }\n\t\t\t\tif (out.target.ref) {\n\t\t\t\t\tout.target.ref = interpolateTokens(out.target.ref, ctx)\n\t\t\t\t}\n\t\t\t\tif (out.target.selector) {\n\t\t\t\t\tout.target.selector = interpolateTokens(out.target.selector, ctx)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (out.advanceOn) {\n\t\t\t\tout.advanceOn = { ...out.advanceOn }\n\t\t\t\tif (out.advanceOn.route) {\n\t\t\t\t\tout.advanceOn.route = interpolateTokens(out.advanceOn.route, ctx)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t}\n\n\t\t/**\n\t\t * Run a step's `advanceOn.capture` against captured values.\n\t\t *\n\t\t * @param {object} step The active (raw) step.\n\t\t * @param {object} source `{ params?, object? }` to read captured values from.\n\t\t * @return {void}\n\t\t */\n\t\tfunction runCapture(step, source) {\n\t\t\tconst cap = step && step.advanceOn && step.advanceOn.capture\n\t\t\tif (!cap) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst next = { ...context.value }\n\t\t\tfor (const [varName, token] of Object.entries(cap)) {\n\t\t\t\tconst key = String(token).replace(/^:/, '')\n\t\t\t\tlet value\n\t\t\t\tif (source.params && key in source.params) {\n\t\t\t\t\tvalue = source.params[key]\n\t\t\t\t} else if (source.object) {\n\t\t\t\t\tvalue = (key === 'id') ? (source.object.id ?? source.object['@self']?.id ?? source.object.uuid) : source.object[key]\n\t\t\t\t}\n\t\t\t\tif (value !== null && value !== undefined) {\n\t\t\t\t\tnext[varName] = value\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontext.value = next\n\t\t}\n\n\t\t/**\n\t\t * Advance to the next step, or complete on the last step. Skips\n\t\t * subsequent `optional` steps whose target ref is empty after interpolation.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction next() {\n\t\t\tif (!activeTour.value) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (currentIndex.value >= totalSteps.value - 1) {\n\t\t\t\tcomplete()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcurrentIndex.value += 1\n\t\t}\n\t\t/**\n\t\t * Step back (no-op on the first step).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction back() {\n\t\t\tif (currentIndex.value > 0) {\n\t\t\t\tcurrentIndex.value -= 1\n\t\t\t}\n\t\t}\n\t\t/**\n\t\t * Skip the active step (advance without satisfying its condition).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction skip() {\n\t\t\tnext()\n\t\t}\n\t\t/**\n\t\t * Jump to a step by id within the active tour.\n\t\t *\n\t\t * @param {string} stepId The target step id.\n\t\t * @return {void}\n\t\t */\n\t\tfunction jumpTo(stepId) {\n\t\t\tif (!activeTour.value) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst idx = activeTour.value.steps.findIndex((s) => s.id === stepId)\n\t\t\tif (idx >= 0) {\n\t\t\t\tcurrentIndex.value = idx\n\t\t\t}\n\t\t}\n\t\t/**\n\t\t * Start a tour by id at its first (resume) step.\n\t\t *\n\t\t * @param {string} tourId The tour id.\n\t\t * @param {number} [startIndex] Optional resume index.\n\t\t * @return {void}\n\t\t */\n\t\tfunction start(tourId, startIndex = 0) {\n\t\t\treplaying.value = false\n\t\t\tpaused.value = false\n\t\t\tactiveTourId.value = tourId\n\t\t\tcurrentIndex.value = startIndex\n\t\t\tcontext.value = {}\n\t\t\trunning.value = true\n\t\t}\n\t\t/**\n\t\t * Restart a tour from the beginning (used by the \"Restart tutorial\" /\n\t\t * \"Replay\" entry). Runs in replay mode so the FULL tour shows even for a\n\t\t * returning user whose persisted seen-version already covers every step.\n\t\t *\n\t\t * @param {string} tourId The tour id.\n\t\t * @return {void}\n\t\t */\n\t\tfunction restart(tourId) {\n\t\t\tstart(tourId, 0)\n\t\t\treplaying.value = true\n\t\t}\n\t\t/**\n\t\t * Dismiss the active tour without marking it complete.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction dismiss() {\n\t\t\trunning.value = false\n\t\t\tpaused.value = false\n\t\t\tactiveTourId.value = null\n\t\t\treplaying.value = false\n\t\t}\n\t\t/**\n\t\t * Hide the active tour without ending it (ESC, a click on the dim).\n\t\t * The tour and step are kept, so `resumePaused()` continues there.\n\t\t *\n\t\t * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n\t\t * @return {void}\n\t\t */\n\t\tfunction pause() {\n\t\t\tif (!activeTourId.value) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\trunning.value = false\n\t\t\tpaused.value = true\n\t\t}\n\t\t/**\n\t\t * Show a paused tour again at the step it was on.\n\t\t *\n\t\t * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n\t\t * @return {boolean} True when there was a paused tour to show.\n\t\t */\n\t\tfunction resumePaused() {\n\t\t\tif (!paused.value || !activeTourId.value) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tpaused.value = false\n\t\t\trunning.value = true\n\t\t\treturn true\n\t\t}\n\t\t/**\n\t\t * Start a tour at a remembered step (\"continue where you left off\").\n\t\t * Runs in replay mode so a returning user sees the full tour, and\n\t\t * falls back to the first step when the step no longer exists.\n\t\t *\n\t\t * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n\t\t * @param {string} tourId The tour id.\n\t\t * @param {string} [stepId] The step to continue at.\n\t\t * @return {boolean} True when the tour exists and started.\n\t\t */\n\t\tfunction resumeAt(tourId, stepId) {\n\t\t\tconst tour = tours.find((t) => t.id === tourId)\n\t\t\tif (!tour) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treplaying.value = true\n\t\t\tconst idx = composeSteps(tour).findIndex((s) => s.id === stepId)\n\t\t\tstart(tourId, Math.max(0, idx))\n\t\t\treplaying.value = true\n\t\t\treturn true\n\t\t}\n\t\t/**\n\t\t * Mark the active tour complete: stop running and invoke the\n\t\t * `onComplete(appVersion)` persistence callback so the seen version is\n\t\t * recorded by the consumer (CnAppRoot).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction complete() {\n\t\t\trunning.value = false\n\t\t\tpaused.value = false\n\t\t\tactiveTourId.value = null\n\t\t\treplaying.value = false\n\t\t\tif (typeof options.onComplete === 'function') {\n\t\t\t\toptions.onComplete(appVersion)\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Feed a declarative advance signal to the active step. When the signal\n\t\t * satisfies the step's `advanceOn`, captures run and the tour advances.\n\t\t *\n\t\t * @param {object} signal `{ kind: 'route'|'object-created'|'element'|'click'|'delay', route?, params?, object? }`.\n\t\t * @return {boolean} Whether the signal advanced the tour.\n\t\t */\n\t\tfunction notify(signal) {\n\t\t\tconst step = activeTour.value && activeTour.value.steps[currentIndex.value]\n\t\t\tif (!step || !step.advanceOn) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst a = step.advanceOn\n\t\t\tlet match = false\n\t\t\tif (signal.kind === 'route' && a.type === 'route-match') {\n\t\t\t\tmatch = signal.route === a.route\n\t\t\t\tif (match) {\n\t\t\t\t\trunCapture(step, { params: signal.params || {} })\n\t\t\t\t}\n\t\t\t} else if (signal.kind === 'object-created' && a.type === 'object-created') {\n\t\t\t\tmatch = objectCreatedMatches(a, signal)\n\t\t\t\tif (match) {\n\t\t\t\t\trunCapture(step, { object: signal.object || {} })\n\t\t\t\t}\n\t\t\t} else if (signal.kind === 'element' && a.type === 'element-appears') {\n\t\t\t\tmatch = true\n\t\t\t} else if (signal.kind === 'click' && a.type === 'click-target') {\n\t\t\t\tmatch = true\n\t\t\t} else if (signal.kind === 'delay' && a.type === 'delay') {\n\t\t\t\tmatch = true\n\t\t\t}\n\t\t\tif (match) {\n\t\t\t\tnext()\n\t\t\t}\n\t\t\treturn match\n\t\t}\n\n\t\tentry = {\n\t\t\tenabled,\n\t\t\tappVersion,\n\t\t\tseenVersion,\n\t\t\ttours,\n\t\t\tactiveTour,\n\t\t\tautoStartTour,\n\t\t\tcurrentStep,\n\t\t\ttotalSteps,\n\t\t\tisFirst,\n\t\t\tisLast,\n\t\t\tcontext,\n\t\t\trunning,\n\t\t\tpaused,\n\t\t\treplaying,\n\t\t\tstart,\n\t\t\trestart,\n\t\t\tpause,\n\t\t\tresumePaused,\n\t\t\tresumeAt,\n\t\t\tnext,\n\t\t\tback,\n\t\t\tskip,\n\t\t\tjumpTo,\n\t\t\tdismiss,\n\t\t\tcomplete,\n\t\t\tnotify,\n\t\t\tinterpolate: (s) => interpolateTokens(s, context.value),\n\t\t}\n\t\tcache.set(appId, entry)\n\n\t\t// Resume token (cross-app hand-off / refresh) — start at the resume step.\n\t\tif (options.resume && options.resume.tourId) {\n\t\t\tconst tour = tours.find((t) => t.id === options.resume.tourId)\n\t\t\tif (tour) {\n\t\t\t\tconst steps = composeSteps(tour)\n\t\t\t\tconst idx = Math.max(0, steps.findIndex((s) => s.id === options.resume.stepId))\n\t\t\t\tstart(options.resume.tourId, idx)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn entry\n}\n\n/**\n * Test-only helper to reset the per-`appId` cache.\n *\n * @internal\n */\nexport function __resetWalkthroughCacheForTests() {\n\tcache.clear()\n}\n"],"names":["axios"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useWalkthrough.js","sources":["../../../src/composables/useWalkthrough.js"],"sourcesContent":["/**\n * SPDX-FileCopyrightText: 2026 Conduction B.V.\n * SPDX-License-Identifier: EUPL-1.2\n */\n\nimport axios from '@nextcloud/axios'\nimport { generateUrl } from '@nextcloud/router'\nimport { computed, ref } from 'vue'\n\n/**\n * Per-`appId` cache so all consumers of one app share a single walkthrough\n * machine + persisted progress for the page lifetime (mirrors useSetupStatus).\n */\nconst cache = new Map()\n\n/**\n * localStorage key prefix for the per-browser mirror of the last-seen version.\n * The AUTHORITATIVE store is the per-user server preference addressed by\n * `manifest.walkthrough.completionConfigKey`; this is the synchronous cache /\n * offline fallback so the tour never flashes before the GET resolves.\n */\nexport const WALKTHROUGH_SEEN_STORAGE_PREFIX = 'cn-walkthrough-seen:'\n\n/**\n * Build the per-user preferences endpoint for a walkthrough completion key.\n * Read (`GET`) and write (`PUT`) MUST address the same URL — a persist that\n * lands on a different key reads back as \"never seen\" forever.\n *\n * @param {string} appId The Nextcloud app id.\n * @param {string} configKey `manifest.walkthrough.completionConfigKey`.\n * @return {string} The generated `/apps/{appId}/api/preferences/{key}` URL.\n */\nexport function walkthroughPreferenceUrl(appId, configKey) {\n\treturn generateUrl('/apps/' + appId + '/api/preferences/' + configKey)\n}\n\n/**\n * Normalise a stored completion value into the string the version engine\n * compares against.\n *\n * Only `null` / `undefined` / `''` mean \"never seen\". Anything else counts as\n * seen — including the JS-falsy scalars `false`, `0` and `'0'`, which a plain\n * truthiness check would misread as a fresh user and re-open the tour on every\n * visit. This is the read-side counterpart of `persistWalkthroughSeenVersion`.\n *\n * @param {unknown} value The raw stored / API value.\n * @return {string} The last-seen version, or `''` when never seen.\n */\nexport function normaliseSeenVersion(value) {\n\tif (value === null || value === undefined) {\n\t\treturn ''\n\t}\n\tif (typeof value === 'string') {\n\t\treturn value\n\t}\n\treturn String(value)\n}\n\n/**\n * Resolve the injectable `localStorage` backend (null in SSR / locked-down\n * browsers, where persistence degrades to session-only).\n *\n * @param {Storage} [injected] Test-injected storage.\n * @return {Storage|null} The storage backend or null.\n */\nfunction resolveStorage(injected) {\n\tif (injected) {\n\t\treturn injected\n\t}\n\tif (typeof window === 'undefined') {\n\t\treturn null\n\t}\n\ttry {\n\t\treturn window.localStorage\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Read the per-browser mirror of the last-seen walkthrough version.\n * Synchronous, so callers can seed state before the server GET resolves.\n *\n * @param {string} appId The Nextcloud app id.\n * @param {Storage} [storage] Injectable storage backend.\n * @return {string} The last-seen version, or `''`.\n */\nexport function readLocalWalkthroughSeenVersion(appId, storage) {\n\tconst s = resolveStorage(storage)\n\tif (!s) {\n\t\treturn ''\n\t}\n\ttry {\n\t\treturn normaliseSeenVersion(s.getItem(WALKTHROUGH_SEEN_STORAGE_PREFIX + appId))\n\t} catch {\n\t\treturn ''\n\t}\n}\n\n/**\n * Write the per-browser mirror of the last-seen walkthrough version.\n *\n * @param {string} appId The Nextcloud app id.\n * @param {string} version The version to record.\n * @param {Storage} [storage] Injectable storage backend.\n * @return {void}\n */\nfunction writeLocalWalkthroughSeenVersion(appId, version, storage) {\n\tconst s = resolveStorage(storage)\n\tif (!s) {\n\t\treturn\n\t}\n\ttry {\n\t\ts.setItem(WALKTHROUGH_SEEN_STORAGE_PREFIX + appId, version)\n\t} catch {\n\t\t/* quota / private mode — persistence is best-effort */\n\t}\n}\n\n/**\n * Type guard — true for a plain (non-null, non-array) object. Used to reject\n * the SPA index HTML Nextcloud returns (with status 200) when an app does not\n * actually serve `/api/preferences/{key}`.\n *\n * @param {unknown} value Candidate.\n * @return {boolean} True when value is a plain object.\n */\nfunction isPlainObject(value) {\n\treturn value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\n/**\n * Load the user's last-seen walkthrough version (ADR-043, REQ-WALK-NV-004).\n *\n * The per-USER server preference addressed by `completionConfigKey` is\n * authoritative (cross-device); the localStorage mirror is the fallback used\n * when no key is declared, the endpoint is absent, or the request fails. A\n * `{ value: null }` response is a definitive \"never seen\" and correctly yields\n * the local mirror (usually `''`).\n *\n * @param {string} appId The Nextcloud app id.\n * @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).\n * @param {object} [options] `{ http, storage }` injection points for tests.\n * @return {Promise<string>} The last-seen version, or `''` for a fresh user.\n */\nexport async function loadWalkthroughSeenVersion(appId, configKey, options = {}) {\n\tconst local = readLocalWalkthroughSeenVersion(appId, options.storage)\n\tif (!configKey) {\n\t\treturn local\n\t}\n\tconst http = options.http || axios\n\ttry {\n\t\tconst { data } = await http.get(walkthroughPreferenceUrl(appId, configKey))\n\t\t// Only a real preferences payload counts. An HTML string (SPA fallback\n\t\t// from an app that doesn't serve the route) is NOT a \"never seen\"\n\t\t// signal — fall back to the local mirror instead.\n\t\tif (!isPlainObject(data) || !('value' in data)) {\n\t\t\treturn local\n\t\t}\n\t\tconst seen = normaliseSeenVersion(data.value)\n\t\tif (seen) {\n\t\t\twriteLocalWalkthroughSeenVersion(appId, seen, options.storage)\n\t\t\treturn seen\n\t\t}\n\t\treturn local\n\t} catch {\n\t\t// Unauthenticated / endpoint missing / offline.\n\t\treturn local\n\t}\n}\n\n/**\n * Persist the user's last-seen walkthrough version (ADR-043, REQ-WALK-NV-006).\n *\n * Writes the localStorage mirror synchronously AND `PUT`s the same\n * `completionConfigKey` preference the load path reads, so a returning user —\n * on this browser or any other — is not shown the tour again. Never throws:\n * a failed write only means the tour may re-open elsewhere.\n *\n * @param {string} appId The Nextcloud app id.\n * @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).\n * @param {string} version The app version to record as seen.\n * @param {object} [options] `{ http, storage }` injection points for tests.\n * @return {Promise<boolean>} True when the server preference was written.\n */\nexport function persistWalkthroughSeenVersion(appId, configKey, version, options = {}) {\n\tconst value = normaliseSeenVersion(version)\n\twriteLocalWalkthroughSeenVersion(appId, value, options.storage)\n\tif (!configKey) {\n\t\treturn Promise.resolve(false)\n\t}\n\tconst http = options.http || axios\n\ttry {\n\t\treturn Promise.resolve(http.put(walkthroughPreferenceUrl(appId, configKey), { value }))\n\t\t\t.then(() => true)\n\t\t\t.catch(() => false)\n\t} catch {\n\t\treturn Promise.resolve(false)\n\t}\n}\n\n/**\n * localStorage key prefix for the per-browser mirror of tour progress.\n */\nexport const WALKTHROUGH_PROGRESS_STORAGE_PREFIX = 'cn-walkthrough-progress:'\n\n/**\n * The preference key that holds tour progress, next to the completion key.\n *\n * @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).\n * @return {string} `<configKey>-progress`, or '' when no key is declared.\n */\nexport function walkthroughProgressKey(configKey) {\n\treturn configKey ? configKey + '-progress' : ''\n}\n\n/**\n * Normalise stored progress (an object, or the JSON string a preference\n * holds) into `{ tourId, stepId, index, version }`, plus `paused: true`\n * when the user paused the tour, or null.\n *\n * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n * @spec openspec/changes/audit-round-lib-fixes/specs/cn-walkthrough/spec.md\n * @param {unknown} value The stored value.\n * @return {{tourId: string, stepId: string, index: number, version: string, paused: (boolean|undefined)}|null} The progress.\n */\nexport function normaliseWalkthroughProgress(value) {\n\tlet data = value\n\tif (typeof data === 'string') {\n\t\tif (data === '') {\n\t\t\treturn null\n\t\t}\n\t\ttry {\n\t\t\tdata = JSON.parse(data)\n\t\t} catch {\n\t\t\treturn null\n\t\t}\n\t}\n\tif (!isPlainObject(data) || typeof data.tourId !== 'string' || data.tourId === '') {\n\t\treturn null\n\t}\n\tconst progress = {\n\t\ttourId: data.tourId,\n\t\tstepId: typeof data.stepId === 'string' ? data.stepId : '',\n\t\tindex: Number.isInteger(data.index) && data.index >= 0 ? data.index : 0,\n\t\tversion: typeof data.version === 'string' ? data.version : '',\n\t}\n\t// A paused tour stays hidden on the next visit until the user picks\n\t// \"Continue\". Only written when true, so older values read unchanged.\n\tif (data.paused === true) {\n\t\tprogress.paused = true\n\t}\n\treturn progress\n}\n\n/**\n * Read the per-browser mirror of the tour progress. Synchronous.\n *\n * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n * @param {string} appId The Nextcloud app id.\n * @param {Storage} [storage] Injectable storage backend.\n * @return {object|null} The progress, or null.\n */\nexport function readLocalWalkthroughProgress(appId, storage) {\n\tconst s = resolveStorage(storage)\n\tif (!s) {\n\t\treturn null\n\t}\n\ttry {\n\t\treturn normaliseWalkthroughProgress(s.getItem(WALKTHROUGH_PROGRESS_STORAGE_PREFIX + appId))\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * Load the user's tour progress: the per-user preference\n * `<completionConfigKey>-progress` when declared, else the local mirror.\n *\n * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n * @param {string} appId The Nextcloud app id.\n * @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).\n * @param {object} [options] `{ http, storage }` injection points for tests.\n * @return {Promise<object|null>} The progress, or null.\n */\nexport async function loadWalkthroughProgress(appId, configKey, options = {}) {\n\tconst local = readLocalWalkthroughProgress(appId, options.storage)\n\tconst key = walkthroughProgressKey(configKey)\n\tif (!key) {\n\t\treturn local\n\t}\n\tconst http = options.http || axios\n\ttry {\n\t\tconst { data } = await http.get(walkthroughPreferenceUrl(appId, key))\n\t\tif (!isPlainObject(data) || !('value' in data)) {\n\t\t\treturn local\n\t\t}\n\t\treturn normaliseWalkthroughProgress(data.value) || local\n\t} catch {\n\t\treturn local\n\t}\n}\n\n/**\n * Persist the user's tour progress, or clear it with `null`. Writes the\n * local mirror and, with a completion key, the per-user preference. Never\n * throws.\n *\n * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n * @param {string} appId The Nextcloud app id.\n * @param {string} configKey `manifest.walkthrough.completionConfigKey` (may be empty).\n * @param {object|null} progress `{ tourId, stepId, index, version }`, or null to clear.\n * @param {object} [options] `{ http, storage }` injection points for tests.\n * @return {Promise<boolean>} True when the server preference was written.\n */\nexport function persistWalkthroughProgress(appId, configKey, progress, options = {}) {\n\tconst value = progress ? normaliseWalkthroughProgress(progress) : null\n\tconst raw = value ? JSON.stringify(value) : ''\n\tconst s = resolveStorage(options.storage)\n\tif (s) {\n\t\ttry {\n\t\t\tif (raw) {\n\t\t\t\ts.setItem(WALKTHROUGH_PROGRESS_STORAGE_PREFIX + appId, raw)\n\t\t\t} else {\n\t\t\t\ts.removeItem(WALKTHROUGH_PROGRESS_STORAGE_PREFIX + appId)\n\t\t\t}\n\t\t} catch {\n\t\t\t/* quota / private mode: best-effort */\n\t\t}\n\t}\n\tconst key = walkthroughProgressKey(configKey)\n\tif (!key) {\n\t\treturn Promise.resolve(false)\n\t}\n\tconst http = options.http || axios\n\ttry {\n\t\treturn Promise.resolve(http.put(walkthroughPreferenceUrl(appId, key), { value: raw }))\n\t\t\t.then(() => true)\n\t\t\t.catch(() => false)\n\t} catch {\n\t\treturn Promise.resolve(false)\n\t}\n}\n\n/**\n * Whether an `object-created` signal satisfies a step's `advanceOn`.\n *\n * The signal's own `register` / `schema` (the slugs the creating surface\n * knows) are compared first; an object's `@self` values are numeric ids on\n * OpenRegister and only match a step that also uses ids.\n *\n * @param {object} advanceOn The step's `advanceOn`.\n * @param {object} signal `{ register?, schema?, object? }`.\n * @return {boolean} True when it matches.\n */\nfunction objectCreatedMatches(advanceOn, signal) {\n\tconst obj = signal.object || {}\n\tconst self = obj['@self'] || {}\n\tconst same = (want, candidates) => !want || candidates\n\t\t.filter((c) => c !== null && c !== undefined && c !== '')\n\t\t.some((c) => String(c).toLowerCase() === String(want).toLowerCase())\n\treturn same(advanceOn.register, [signal.register, obj.register, self.register])\n\t\t&& same(advanceOn.schema, [signal.schema, obj.schema, self.schema])\n}\n\n/**\n * Compare two semver-ish strings. Missing / unparseable parts sort as 0.\n *\n * @param {string} a Left version.\n * @param {string} b Right version.\n * @return {number} -1 / 0 / 1.\n */\nexport function compareSemver(a, b) {\n\tconst pa = String(a || '0').split('.').map((n) => parseInt(n, 10) || 0)\n\tconst pb = String(b || '0').split('.').map((n) => parseInt(n, 10) || 0)\n\tfor (let i = 0; i < Math.max(pa.length, pb.length); i++) {\n\t\tconst d = (pa[i] || 0) - (pb[i] || 0)\n\t\tif (d !== 0) {\n\t\t\treturn d < 0 ? -1 : 1\n\t\t}\n\t}\n\treturn 0\n}\n\n/**\n * Replace `{{var}}` tokens in a string from a context bag. Unknown keys are\n * left intact (and warned in dev) so a misauthored tour degrades visibly.\n *\n * @param {string} input The template string.\n * @param {object} context The context bag.\n * @return {string} The interpolated string.\n */\nexport function interpolateTokens(input, context) {\n\tif (typeof input !== 'string' || input.indexOf('{{') === -1) {\n\t\treturn input\n\t}\n\treturn input.replace(/\\{\\{\\s*([\\w.-]+)\\s*\\}\\}/g, (m, key) => {\n\t\tif (context && Object.hasOwn(context, key) && context[key] !== null && context[key] !== undefined) {\n\t\t\treturn String(context[key])\n\t\t}\n\t\treturn m\n\t})\n}\n\n/**\n * Walkthrough engine (ADR-043). Loads `manifest.walkthrough`, composes the\n * active tour for the current user + app version, and runs a step machine whose\n * advancement is fed declarative *signals* (route change, object created,\n * element appeared, click, delay) by the rendering component — keeping this\n * composable pure and unit-testable. Captured route params / object ids land in\n * a context bag interpolated into later steps via `{{var}}`.\n *\n * Persistence is decoupled: pass `seenVersion` (the user's last-seen app version)\n * and an `onComplete(appVersion)` callback. CnAppRoot wires these to the per-user\n * `manifest.walkthrough.completionConfigKey` preference via\n * {@link loadWalkthroughSeenVersion} / {@link persistWalkthroughSeenVersion}.\n *\n * @param {string} appId The Nextcloud app id (cache key).\n * @param {object} manifest The app manifest (reads `manifest.walkthrough` + `manifest.version`).\n * @param {object} [options] `{ appVersion, seenVersion, resume, onComplete }`.\n * @return {object} The reactive machine + controls (see properties below).\n *\n * @example\n * const wt = useWalkthrough('pipelinq', manifest, { seenVersion: '1.0.0' })\n * wt.start('getting-started')\n * wt.notify({ kind: 'route', route: 'Products', params: { id: '42' } })\n */\nexport function useWalkthrough(appId, manifest, options = {}) {\n\tlet entry = cache.get(appId)\n\tif (!entry) {\n\t\tconst walkthrough = (manifest && manifest.walkthrough) || {}\n\t\tconst appVersion = options.appVersion || (manifest && manifest.version) || '0.0.0'\n\t\t// Normalised, NOT `|| ''`: a completed user whose recorded version is a\n\t\t// JS-falsy scalar (`0` / `false`) must still read as \"has seen it\".\n\t\tconst seenVersion = normaliseSeenVersion(options.seenVersion)\n\t\tconst enabled = walkthrough.enabled !== false && Array.isArray(walkthrough.tours) && walkthrough.tours.length > 0\n\n\t\tconst tours = walkthrough.tours || []\n\t\tconst activeTourId = ref(null)\n\t\tconst currentIndex = ref(0)\n\t\tconst context = ref({})\n\t\tconst running = ref(false)\n\t\t// True while the tour is hidden by ESC or a backdrop click. The tour\n\t\t// and its step stay, so it continues where it was.\n\t\tconst paused = ref(false)\n\t\t// True while a user-initiated replay is active (the \"Restart tutorial\"\n\t\t// entry). A replay shows the FULL tour regardless of the persisted\n\t\t// seen-version — otherwise a returning user, whose seenVersion already\n\t\t// covers every step's sinceVersion, would replay an empty tour.\n\t\tconst replaying = ref(false)\n\n\t\t/**\n\t\t * Compose the step set for a tour, filtered by version: a fresh user\n\t\t * (no seenVersion) gets all steps `<= appVersion`; an upgraded user gets\n\t\t * only steps newer than seenVersion and `<= appVersion`. A user-initiated\n\t\t * replay (`replaying`) ignores seenVersion and shows every step\n\t\t * `<= appVersion`.\n\t\t *\n\t\t * @param {object} tour The tour definition.\n\t\t * @return {Array} The visible steps for this user.\n\t\t */\n\t\tfunction composeSteps(tour) {\n\t\t\tconst all = Array.isArray(tour.steps) ? tour.steps : []\n\t\t\treturn all.filter((s) => {\n\t\t\t\tconst since = s.sinceVersion || '0.0.0'\n\t\t\t\tif (compareSemver(since, appVersion) > 0) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tif (!replaying.value && seenVersion && compareSemver(since, seenVersion) <= 0) {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t}\n\n\t\tconst activeTour = computed(() => {\n\t\t\tif (!activeTourId.value) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst tour = tours.find((t) => t.id === activeTourId.value)\n\t\t\tif (!tour) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn { ...tour, steps: composeSteps(tour) }\n\t\t})\n\n\t\t/**\n\t\t * The tour that should auto-start for this user, or null. A\n\t\t * `version-bump` tour only qualifies when it has a non-empty step delta;\n\t\t * `first-visit` qualifies only when the user has never seen the tour.\n\t\t *\n\t\t * @return {object|null} The auto-start tour (raw definition) or null.\n\t\t */\n\t\tconst autoStartTour = computed(() => {\n\t\t\tif (!enabled) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tfor (const tour of tours) {\n\t\t\t\tif (tour.minAppVersion && compareSemver(appVersion, tour.minAppVersion) < 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tconst steps = composeSteps(tour)\n\t\t\t\tif (steps.length === 0) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif (tour.trigger === 'first-visit' && !seenVersion) {\n\t\t\t\t\treturn tour\n\t\t\t\t}\n\t\t\t\tif (tour.trigger === 'version-bump' && seenVersion && compareSemver(appVersion, seenVersion) > 0) {\n\t\t\t\t\treturn tour\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn null\n\t\t})\n\n\t\tconst currentStep = computed(() => {\n\t\t\tconst t = activeTour.value\n\t\t\tif (!t) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst raw = t.steps[currentIndex.value]\n\t\t\tif (!raw) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn interpolateStep(raw, context.value)\n\t\t})\n\t\tconst totalSteps = computed(() => (activeTour.value ? activeTour.value.steps.length : 0))\n\t\tconst isFirst = computed(() => currentIndex.value === 0)\n\t\tconst isLast = computed(() => totalSteps.value > 0 && currentIndex.value === totalSteps.value - 1)\n\n\t\t/**\n\t\t * Apply `{{var}}` interpolation to a step's user-facing + targeting fields.\n\t\t *\n\t\t * @param {object} step The raw step.\n\t\t * @param {object} ctx The context bag.\n\t\t * @return {object} A shallow copy with tokens resolved.\n\t\t */\n\t\tfunction interpolateStep(step, ctx) {\n\t\t\tconst out = { ...step }\n\t\t\tfor (const k of ['title', 'body', 'task']) {\n\t\t\t\tif (out[k]) {\n\t\t\t\t\tout[k] = interpolateTokens(out[k], ctx)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (out.target) {\n\t\t\t\tout.target = { ...out.target }\n\t\t\t\tif (out.target.ref) {\n\t\t\t\t\tout.target.ref = interpolateTokens(out.target.ref, ctx)\n\t\t\t\t}\n\t\t\t\tif (out.target.selector) {\n\t\t\t\t\tout.target.selector = interpolateTokens(out.target.selector, ctx)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (out.advanceOn) {\n\t\t\t\tout.advanceOn = { ...out.advanceOn }\n\t\t\t\tif (out.advanceOn.route) {\n\t\t\t\t\tout.advanceOn.route = interpolateTokens(out.advanceOn.route, ctx)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t}\n\n\t\t/**\n\t\t * Run a step's `advanceOn.capture` against captured values.\n\t\t *\n\t\t * @param {object} step The active (raw) step.\n\t\t * @param {object} source `{ params?, object? }` to read captured values from.\n\t\t * @return {void}\n\t\t */\n\t\tfunction runCapture(step, source) {\n\t\t\tconst cap = step && step.advanceOn && step.advanceOn.capture\n\t\t\tif (!cap) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst next = { ...context.value }\n\t\t\tfor (const [varName, token] of Object.entries(cap)) {\n\t\t\t\tconst key = String(token).replace(/^:/, '')\n\t\t\t\tlet value\n\t\t\t\tif (source.params && key in source.params) {\n\t\t\t\t\tvalue = source.params[key]\n\t\t\t\t} else if (source.object) {\n\t\t\t\t\tvalue = (key === 'id') ? (source.object.id ?? source.object['@self']?.id ?? source.object.uuid) : source.object[key]\n\t\t\t\t}\n\t\t\t\tif (value !== null && value !== undefined) {\n\t\t\t\t\tnext[varName] = value\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontext.value = next\n\t\t}\n\n\t\t/**\n\t\t * Advance to the next step, or complete on the last step. Skips\n\t\t * subsequent `optional` steps whose target ref is empty after interpolation.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction next() {\n\t\t\tif (!activeTour.value) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif (currentIndex.value >= totalSteps.value - 1) {\n\t\t\t\tcomplete()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcurrentIndex.value += 1\n\t\t}\n\t\t/**\n\t\t * Step back (no-op on the first step).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction back() {\n\t\t\tif (currentIndex.value > 0) {\n\t\t\t\tcurrentIndex.value -= 1\n\t\t\t}\n\t\t}\n\t\t/**\n\t\t * Skip the active step (advance without satisfying its condition).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction skip() {\n\t\t\tnext()\n\t\t}\n\t\t/**\n\t\t * Jump to a step by id within the active tour.\n\t\t *\n\t\t * @param {string} stepId The target step id.\n\t\t * @return {void}\n\t\t */\n\t\tfunction jumpTo(stepId) {\n\t\t\tif (!activeTour.value) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst idx = activeTour.value.steps.findIndex((s) => s.id === stepId)\n\t\t\tif (idx >= 0) {\n\t\t\t\tcurrentIndex.value = idx\n\t\t\t}\n\t\t}\n\t\t/**\n\t\t * Start a tour by id at its first (resume) step.\n\t\t *\n\t\t * @param {string} tourId The tour id.\n\t\t * @param {number} [startIndex] Optional resume index.\n\t\t * @return {void}\n\t\t */\n\t\tfunction start(tourId, startIndex = 0) {\n\t\t\treplaying.value = false\n\t\t\tpaused.value = false\n\t\t\tactiveTourId.value = tourId\n\t\t\tcurrentIndex.value = startIndex\n\t\t\tcontext.value = {}\n\t\t\trunning.value = true\n\t\t}\n\t\t/**\n\t\t * Restart a tour from the beginning (used by the \"Restart tutorial\" /\n\t\t * \"Replay\" entry). Runs in replay mode so the FULL tour shows even for a\n\t\t * returning user whose persisted seen-version already covers every step.\n\t\t *\n\t\t * @param {string} tourId The tour id.\n\t\t * @return {void}\n\t\t */\n\t\tfunction restart(tourId) {\n\t\t\tstart(tourId, 0)\n\t\t\treplaying.value = true\n\t\t}\n\t\t/**\n\t\t * Dismiss the active tour without marking it complete.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction dismiss() {\n\t\t\trunning.value = false\n\t\t\tpaused.value = false\n\t\t\tactiveTourId.value = null\n\t\t\treplaying.value = false\n\t\t}\n\t\t/**\n\t\t * Hide the active tour without ending it (ESC, a click on the dim).\n\t\t * The tour and step are kept, so `resumePaused()` continues there.\n\t\t *\n\t\t * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n\t\t * @return {void}\n\t\t */\n\t\tfunction pause() {\n\t\t\tif (!activeTourId.value) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\trunning.value = false\n\t\t\tpaused.value = true\n\t\t}\n\t\t/**\n\t\t * Show a paused tour again at the step it was on.\n\t\t *\n\t\t * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n\t\t * @return {boolean} True when there was a paused tour to show.\n\t\t */\n\t\tfunction resumePaused() {\n\t\t\tif (!paused.value || !activeTourId.value) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tpaused.value = false\n\t\t\trunning.value = true\n\t\t\treturn true\n\t\t}\n\t\t/**\n\t\t * Start a tour at a remembered step (\"continue where you left off\").\n\t\t * Runs in replay mode so a returning user sees the full tour, and\n\t\t * falls back to the first step when the step no longer exists.\n\t\t *\n\t\t * @spec openspec/changes/walkthrough-advance-pause-resume/specs/cn-walkthrough/spec.md\n\t\t * @param {string} tourId The tour id.\n\t\t * @param {string} [stepId] The step to continue at.\n\t\t * @return {boolean} True when the tour exists and started.\n\t\t */\n\t\tfunction resumeAt(tourId, stepId) {\n\t\t\tconst tour = tours.find((t) => t.id === tourId)\n\t\t\tif (!tour) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treplaying.value = true\n\t\t\tconst idx = composeSteps(tour).findIndex((s) => s.id === stepId)\n\t\t\tstart(tourId, Math.max(0, idx))\n\t\t\treplaying.value = true\n\t\t\treturn true\n\t\t}\n\t\t/**\n\t\t * Mark the active tour complete: stop running and invoke the\n\t\t * `onComplete(appVersion)` persistence callback so the seen version is\n\t\t * recorded by the consumer (CnAppRoot).\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tfunction complete() {\n\t\t\trunning.value = false\n\t\t\tpaused.value = false\n\t\t\tactiveTourId.value = null\n\t\t\treplaying.value = false\n\t\t\tif (typeof options.onComplete === 'function') {\n\t\t\t\toptions.onComplete(appVersion)\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * Feed a declarative advance signal to the active step. When the signal\n\t\t * satisfies the step's `advanceOn`, captures run and the tour advances.\n\t\t *\n\t\t * @param {object} signal `{ kind: 'route'|'object-created'|'element'|'click'|'delay', route?, params?, object? }`.\n\t\t * @return {boolean} Whether the signal advanced the tour.\n\t\t */\n\t\tfunction notify(signal) {\n\t\t\tconst step = activeTour.value && activeTour.value.steps[currentIndex.value]\n\t\t\tif (!step || !step.advanceOn) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst a = step.advanceOn\n\t\t\tlet match = false\n\t\t\tif (signal.kind === 'route' && a.type === 'route-match') {\n\t\t\t\tmatch = signal.route === a.route\n\t\t\t\tif (match) {\n\t\t\t\t\trunCapture(step, { params: signal.params || {} })\n\t\t\t\t}\n\t\t\t} else if (signal.kind === 'object-created' && a.type === 'object-created') {\n\t\t\t\tmatch = objectCreatedMatches(a, signal)\n\t\t\t\tif (match) {\n\t\t\t\t\trunCapture(step, { object: signal.object || {} })\n\t\t\t\t}\n\t\t\t} else if (signal.kind === 'element' && a.type === 'element-appears') {\n\t\t\t\tmatch = true\n\t\t\t} else if (signal.kind === 'click' && a.type === 'click-target') {\n\t\t\t\tmatch = true\n\t\t\t} else if (signal.kind === 'delay' && a.type === 'delay') {\n\t\t\t\tmatch = true\n\t\t\t}\n\t\t\tif (match) {\n\t\t\t\tnext()\n\t\t\t}\n\t\t\treturn match\n\t\t}\n\n\t\tentry = {\n\t\t\tenabled,\n\t\t\tappVersion,\n\t\t\tseenVersion,\n\t\t\ttours,\n\t\t\tactiveTour,\n\t\t\tautoStartTour,\n\t\t\tcurrentStep,\n\t\t\ttotalSteps,\n\t\t\tisFirst,\n\t\t\tisLast,\n\t\t\tcontext,\n\t\t\trunning,\n\t\t\tpaused,\n\t\t\treplaying,\n\t\t\tstart,\n\t\t\trestart,\n\t\t\tpause,\n\t\t\tresumePaused,\n\t\t\tresumeAt,\n\t\t\tnext,\n\t\t\tback,\n\t\t\tskip,\n\t\t\tjumpTo,\n\t\t\tdismiss,\n\t\t\tcomplete,\n\t\t\tnotify,\n\t\t\tinterpolate: (s) => interpolateTokens(s, context.value),\n\t\t}\n\t\tcache.set(appId, entry)\n\n\t\t// Resume token (cross-app hand-off / refresh) — start at the resume step.\n\t\tif (options.resume && options.resume.tourId) {\n\t\t\tconst tour = tours.find((t) => t.id === options.resume.tourId)\n\t\t\tif (tour) {\n\t\t\t\tconst steps = composeSteps(tour)\n\t\t\t\tconst idx = Math.max(0, steps.findIndex((s) => s.id === options.resume.stepId))\n\t\t\t\tstart(options.resume.tourId, idx)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn entry\n}\n\n/**\n * Test-only helper to reset the per-`appId` cache.\n *\n * @internal\n */\nexport function __resetWalkthroughCacheForTests() {\n\tcache.clear()\n}\n"],"names":["axios"],"mappings":"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"}
|
|
@@ -2841,6 +2841,50 @@ a.cn-kpi-card--stacked:focus-visible {
|
|
|
2841
2841
|
.cn-requires-app.cn-requires-app .empty-content__action {
|
|
2842
2842
|
margin-top: var(--default-grid-baseline, 4px);
|
|
2843
2843
|
}
|
|
2844
|
+
|
|
2845
|
+
/* NcEmptyContent forces its icon's svg to 64px with !important while the
|
|
2846
|
+
* rule above lets the icon box shrink, so the svg spilled out of its box and
|
|
2847
|
+
* over the name. Hold the svg to the size the caller asked for. */
|
|
2848
|
+
.cn-requires-app.cn-requires-app .empty-content__icon svg {
|
|
2849
|
+
width: 32px !important;
|
|
2850
|
+
height: 32px !important;
|
|
2851
|
+
max-width: 32px !important;
|
|
2852
|
+
max-height: 32px !important;
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
/* The dashboard's compact "Install <app>" placeholder: two lines of text and
|
|
2856
|
+
* the button, nothing else, so it fits a two-row tile even under a title row.
|
|
2857
|
+
* `safe center` keeps the top in view when a tile is smaller still, and the
|
|
2858
|
+
* body scrolls rather than clipping the button. */
|
|
2859
|
+
.cn-requires-app--compact {
|
|
2860
|
+
display: flex;
|
|
2861
|
+
flex-direction: column;
|
|
2862
|
+
align-items: center;
|
|
2863
|
+
justify-content: safe center;
|
|
2864
|
+
gap: calc(2 * var(--default-grid-baseline, 4px));
|
|
2865
|
+
box-sizing: border-box;
|
|
2866
|
+
height: 100%;
|
|
2867
|
+
min-height: 0;
|
|
2868
|
+
overflow: auto;
|
|
2869
|
+
padding: calc(2 * var(--default-grid-baseline, 4px));
|
|
2870
|
+
text-align: center;
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
.cn-requires-app--compact .cn-requires-app__description {
|
|
2874
|
+
display: -webkit-box;
|
|
2875
|
+
margin: 0;
|
|
2876
|
+
overflow: hidden;
|
|
2877
|
+
color: var(--color-text-maxcontrast);
|
|
2878
|
+
font-size: 0.9em;
|
|
2879
|
+
line-height: 1.4;
|
|
2880
|
+
-webkit-box-orient: vertical;
|
|
2881
|
+
-webkit-line-clamp: 2;
|
|
2882
|
+
line-clamp: 2;
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
.cn-requires-app--compact .button-vue {
|
|
2886
|
+
flex-shrink: 0;
|
|
2887
|
+
}
|
|
2844
2888
|
/* ========================================
|
|
2845
2889
|
@conduction/nextcloud-vue — CnDetailPage Styles
|
|
2846
2890
|
======================================== */
|
|
@@ -15645,6 +15689,15 @@ a.cn-xwiki-card__title[data-v-54e85fe3]:hover {
|
|
|
15645
15689
|
}
|
|
15646
15690
|
|
|
15647
15691
|
|
|
15692
|
+
/* A select option breaks between words only, never inside one. */
|
|
15693
|
+
.cn-form-dialog__option[data-v-c3eeb0a2] {
|
|
15694
|
+
display: block;
|
|
15695
|
+
min-width: 0;
|
|
15696
|
+
white-space: normal;
|
|
15697
|
+
overflow-wrap: normal;
|
|
15698
|
+
word-break: normal;
|
|
15699
|
+
hyphens: manual;
|
|
15700
|
+
}
|
|
15648
15701
|
.cn-form-dialog__draft-actions[data-v-c3eeb0a2] {
|
|
15649
15702
|
display: flex;
|
|
15650
15703
|
gap: 8px;
|