Omniscio documentation
Browse all documentation
  1. Getting Started13
  2. Sessions & Agents115
  3. Inbox & Notifications59
  4. Projects & Tasks95
  5. Automation & Scheduling75
  6. Knowledge & Memory26
  7. AI Features60
  8. Integrations100
  9. Plugins & Marketplace33
  10. Cloud & Teams56
  11. Settings & Customization58
  12. Account & Billing28
  13. Troubleshooting84
  14. CLI & API Reference22
  15. Legal & Policies4
  16. Uncategorised22

App Tour

Omniscio's guided-onboarding system — the full-screen overlays and spotlight tooltips that walk you through a feature step by step, the tours on offer and where each one surfaces, the JSON that defines them, and how to author a new tour or anchor.

What it is

The App Tour is Omniscio's built-in guided-onboarding system — full-screen overlays and spotlight tooltips that walk you through a feature step by step, highlighting the actual element you'd click and explaining what it does. Tours are opt-in walkthroughs — no tour ever force-runs; you start them yourself. A few also surface a one-time offer toast you can accept or dismiss ("Never show again"): when you first turn on a feature (Gmail, Voice, Screen Capture), and — for the Start a New Hub tour — right after you finish onboarding. You start a tour from Settings → Setup Wizards, which lists every available tour with a checkmark next to the ones you've completed. Picking a tour dims the rest of the app, cuts a spotlight hole around the relevant element, and shows a tooltip card next to it; an overlay step (welcome / completion cards) replaces the spotlight with a centered card. Use Next / Back to step through, Esc to exit, and Skip to mark the tour as completed without finishing it.

A second entry point — the onboarding demo tour — runs once during first-time setup. It's a sandboxed walkthrough of the app inside a fake project with fake sessions and messages (the scene blocks on most steps inject the demo state on the fly so you can see how the inbox, sessions list, and conversation view behave without ever touching real data; the steps that carry a real action or a full-screen overlay do not need one). It is NOT advertised in the Setup Wizards tour list; it's surfaced through the dedicated Get Started entry point on first launch and via Settings → Setup Wizards → Show me around the app again. A few of the demo's steps are hands-on — instead of pressing Next you perform the real action (add a project, start a session, open the session that needs you) and the tour advances when you do; because the demo is sandboxed, nothing real is created (see Hands-on (interactive) steps below).

A third variant — the keyboard demo (onboarding-demo-hotkeys, dev-gated) — is a keystroke-driven copy of the onboarding demo. It reuses the same 34 steps and the same scene blocks (same sandboxed demo data), but each step advances on a key instead of Next/click: the tooltip shows a key-cap and you press it. The keys are the app's REAL shortcuts (N new session, Enter send, R reply, E archive, I inbox, H snooze; Space for explainer steps), resolved from your effective keybindings so they stay correct even if you rebind. Those keys normally fire real, sometimes-paid actions, so the safety model is a hard gate: while this tour is active, AppTour calls useHardGate(...), and the global shortcut dispatch (useKeyboardShortcuts) early-returns on isHardGateOpen() — every real shortcut stands down wholesale, so pressing N only advances the tour (the next step's scene simulates the result) and can never spawn a session. See app-tour-hotkey-demo-contract.md. It's hidden from the Setup Wizards tour list and launched from its own dev-gated Setup Wizards card.

A fourth variant — the Managing Your Sessions practice tour (manage-sessions) — is an on-demand demo tour launched from the toolbar Tours menu (not first-run). Like the onboarding demo it runs on a sandboxed sample workspace: its scene blocks inject a fake project + sessions, and because it's a demo tour (in DEMO_TOUR_IDS), the demo controller (useDemoTour) hides your real sessions and restores them on exit — nothing real is touched. It walks through pausing/resuming, snoozing (opening the snooze picker), undo, and archiving on the sample session — each beat showing the result plus a key-cap (P / H / Ctrl+Z / E) — then narrates the right-click actions (move / rename / color / pin) and the Stop-vs-Pause distinction. It is scene-driven and advances with Next (the key-caps teach the shortcuts; it is NOT keystroke-gated like the keyboard demo, so it needs no hard gate). It launches through its own DemoTourRunner flag (manageSessionsTourActive), strictly parallel to the keyboard demo and off the first-run chain, and is hidden from the plain Setup-Wizards list so it can only run through that demo runner (the plain startTour path would paint its cards over the real app with no sandbox behind them).

Tour authoring is pure JSON. Every tour lives in src/renderer/src/features/app-tour/tours/<slug>.json, gets Zod-validated at app boot via loadAllTours() in tour-loader.ts, and hydrates into the runtime TourDefinition shape. There are no JavaScript helpers (spotlight() / overlay()) any more — you write JSON, the loader does the rest, and if the file fails the schema the app refuses to boot (so a bad tour file is impossible to ship by accident).

Where to find it

How to use it (end-user)

  1. Open Settings → Setup Wizards — every available tour is listed with a checkmark for the ones you've completed.
  2. Click a tour to start it — the rest of the app dims, the relevant element gets a spotlight, and a tooltip appears next to it (or a centered overlay card for welcome / completion steps).
  3. Step through with Next / Back — phase dots at the top of the tooltip show your progress; phases with an empty label (typically the final "completion" phase) are hidden from the dots.
  4. Exit with Esc or Skip — both mark the tour as completed in tourCompletions so the next visit shows a checkmark.
  5. Some tours suggest a follow-up — if the tour's JSON declares a nextTourSuggestion, finishing it pops a toast 1 second later inviting you into the next tour (suppressed for experienced users with ≥ 10 sessions).

The progress dots, Skip button, and Esc handler are all part of the shared <AppTour /> overlay component (it portals into document.body, so it sits above every modal). The actual highlighted element comes from a CSS-mask cutout — the page itself is not modified, so links and buttons under the dim layer remain non-interactive until the tour exits.

A fifth first-run surface is not a JSON tour at all — the Halo Spotlight Tour (HaloSpotlightTour.tsx, mounted globally from GlobalModals.tsx). It is a short, auto-advancing spotlight sequence (3 s per step, a 3 px ring) that walks the Mission Control toolbar and sidebar once, right after onboarding. useHaloSpotlightGate decides whether it fires, and every clause in it is a real guard: not already completed (tourCompletions['halo-spotlight']), onboarding finished, Mission Control the ACTIVE project, no AppTour already running (the two overlays would paint over each other), and desktop-only — the anchors it targets do not exist on mobile. Mission Control is matched through isVirtualProjectActive, not by comparing activeProjectId to the sentinel, because a project UUID never equals a sentinel string and that direct comparison was the bug that stopped this tour firing at all. Because it is not registered in tour-steps.ts, it has no Setup Wizards row and never appears in the tour list below; it is a first-run surface only.

The tours

Slug Label Where it surfaces
core-workflow Your First Session Setup Wizards
sessions-productivity Productivity Power-Ups Setup Wizards
recipes Recipes & Filters Setup Wizards
gmail Gmail Setup Wizards
rss RSS & Webhooks Setup Wizards
automations Filters Setup Wizards
archive-search Archive & Search Setup Wizards
onboarding-tour Welcome to Omniscio Setup Wizards — replay button (behind the Onboarding Tour flag); auto-runs after first-run onboarding
voice-commands Voice Commands Setup Wizards
settings-tour Settings Deep Dive Setup Wizards
start-next-project Start a New Hub Setup Wizards · one-time offer right after onboarding
mindmap Mind Map Gated — Tours menu · in-Map button · first-open offer
writer-onboarding Writer Studio Tour Writer Studio first-open offer (Writer-gated)
sms SMS SMS onboarding flow only
screen-recorder Screen Capture Screen Capture settings — "Take the tour"
onboarding-demo Interactive Demo First-time setup / Get Started
onboarding-demo-hotkeys Keyboard Demo Setup Wizards (dev-gated)
manage-sessions Managing Your Sessions Tours menu — dedicated button → demo runner
tasks-v2 Tasks Tasks MORE menu — "Guided Tour" entry
vault-kms Vault Vault toolbar — "Guided Tour" button
__example__ Placeholder example tour Hidden — bootstrap template

Retired — welcome ("Getting Started"). Removed 2026-07. It spotlighted session-only UI (message box, quick replies, file explorer) that doesn't exist for a brand-new user with nothing set up, so it narrated at empty space; launched from the toolbar over Settings it could also strand the user (its Escape handler swallowed Settings' own Esc-to-close). The sandboxed onboarding demo — surfaced as Interactive Demo, the new-user "Take the tour" CTA, and the first-run walkthrough — is now the single intro tour: it stands up its own sample projects/sessions beneath the walkthrough, so it works with nothing set up. Relatedly, every tour launch now navigates to its home view (dashboard, or the Mind Map) BEFORE the overlay starts, so a tour never renders on top of another screen — see src/renderer/src/app/tour-launch.ts. Guard: welcome-tour-retired.test.ts.

The tours hidden from Setup Wizards (sms, onboarding-demo, onboarding-demo-hotkeys, manage-sessions, __example__, mindmap, writer-onboarding, start-next-project, onboarding-tour, tasks-v2, vault-kms) live in the SETTINGS_MENU_HIDDEN_TOUR_IDS set in tour-steps.ts. They're still resolvable via getTourById() so the surfaces that own them can start them directly. writer-onboarding (Writer Studio Tour) is hidden because Writer Studio is a gated feature — it launches as a first-open offer from the Writer view (WriterView.tsx) only for users with Writer enabled. mindmap is hidden because the Mind Map is a gated (unreleased) feature — its tour is surfaced only through feature-gated entry points (a conditional row in the Tours dropdown, shown when the feature is on, and a "Take a tour" button in the Mind Map toolbar) plus a first-open auto-offer (Gmail-style). Keeping it out of the ungated ALL_TOURS list is what stops it leaking into the Setup Wizards list for users without the feature. See app-tour-mindmap-tour-contract.md. onboarding-demo-hotkeys (the keyboard demo) is likewise dev-gated and launched from its own Setup Wizards card — see app-tour-hotkey-demo-contract.md. manage-sessions (Managing Your Sessions) is hidden for the same structural reason as a demo tour: it must run through its own DemoTourRunner (which stands up the sandbox and hides/restores real sessions), so it's launched from a dedicated Tours-menu button, never the plain startTour path that the ungated list uses. tasks-v2 (Tasks) is hidden because it launches from the Tasks MORE menu's "Guided Tour" entry — it belongs to the Tasks feature, not the general Setup Wizards. vault-kms (Vault) is hidden for the same reason: it launches from a "Guided Tour" button in the Vault's editor toolbar (KmsEditorToolbarStrip.tsx), not the general Setup Wizards — an 8-step spotlight tour covering note creation, formatting, backlinks/tags, Vault Overview, AI agent tools, and appearance settings.

How it behaves

Hands-on (interactive) steps

A spotlight step can carry an interactive block to make it hands-on — the tour hides Next and advances only when the user performs the highlighted action (the active-learning counterpart to read-and-Next). Invariants are locked by app-tour-interactive-contract.md.

{
  "kind": "spotlight",
  "anchor": "sidebar-add-project-button",
  "title": "Adding Projects",
  "description": [{ "type": "text", "text": "This is where you add project folders." }],
  "interactive": { "advanceOn": { "type": "click" } }
}

Two modes:

  • Scripted (the onboarding demo, and the default). The tour's full-screen click-catcher stays on top; a click over the highlighted element is detected via document.elementsFromPoint (so it sees the real target beneath the catcher) and the tour advances — the real handler never fires, so clicking the demo's "Start a session" costs nothing and creates nothing. The next step's scene shows the result. The onboarding demo is ALWAYS scripted, regardless of any flag.
  • Live ("live": true, for real feature tours). The catcher drops pointer-events so the real action runs, and the tour watches the declared advanceOn outcome. Honored ONLY in a non-demo tour on an anchor flagged interactiveSafe in the registry — the guard tests/unit/lint/tour-interactive-safe-anchor.test.ts fails the build otherwise. One tour ships with live today: Welcome to Omniscio (onboarding-tour) runs two hands-on steps, ot-03-open-session and ot-06-open-inbox, where you click the real session row and the real inbox button to advance. The seam exists so other real tours can opt in as their anchors are flagged.

advanceOn — what counts as "done": { "type": "click" } (clicked the highlighted anchor) or { "type": "anchor-appears", "anchor": "<name>" } (a named element mounted — used by live mode to watch an outcome).

Guided-but-rescuable. Next is hidden (you advance by doing), but a "Show me" escape appears if the user stalls (~8s), clicks the wrong spot (with a corrective nudge), or the target never shows — and a missing target degrades to a normal Next step, so nobody is ever stuck.

Authoring one: add "interactive": { "advanceOn": { "type": "click" } } to a spotlight step whose anchor is a clickable control, and make sure the NEXT step's scene depicts the result of the action (in the demo, advancing replays the scripted result).

For agents

JSON shape

Every tour file is one JSON document validated against tourFileSchema in src/renderer/src/features/app-tour/tour-schema.ts. Top-level shape:

{
  "id": "core-workflow",
  "label": "Your First Session",
  "description": "The complete cycle: project to finished work",
  "nextTourSuggestion": {
    "tourId": "sessions-productivity",
    "message": "Nice! Want to learn the power features? The Productivity tour covers quick replies and shortcuts."
  },
  "phaseLabels": [
    { "phase": "welcome", "label": "" },
    { "phase": "projects", "label": "Projects" },
    { "phase": "completion", "label": "" }
  ],
  "steps": [ ... ]
}
  • id — must be one of the TourId union literals in tour-types.ts. Adding a brand-new tour means extending that union.
  • label — human-readable title shown in Setup Wizards and in the tooltip header.
  • description — one-line subtitle for the Setup Wizards card; not shown inside the running tour.
  • nextTourSuggestion (optional) — fires a follow-up toast on completion. Skipped if the suggested tour is already completed, or if the user has ≥ 10 sessions (treated as "experienced, don't nag").
  • phaseLabels — drives the phase dots. Empty label hides the dot for that phase.
  • steps — non-empty array, each step is either spotlight or overlay.

Spotlight steps

{
  "kind": "spotlight",
  "anchor": "sidebar-projects-list",
  "title": "Your Projects",
  "placement": "right",
  "padding": 4,
  "borderRadius": 12,
  "phase": "workspace",
  "description": [
    { "type": "text", "text": "Each project maps to a local folder on your machine." },
    { "type": "bullets", "items": ["Add folders", "Organize with dividers", "Color-code"] },
    { "type": "tip", "text": "The Inbox at the top collects sessions needing attention." }
  ]
}
  • Target — exactly ONE of anchor / selector / text (a spotlight step must set precisely one; the schema rejects zero or two). This is the "highlight literally anything" seam — see Flexible targeting below.
    • anchor — names a DOM element via data-ui-anchor="<anchor>". When set, it MUST be registered: the CI gate (tests/unit/lint/tour-anchor-coverage.test.ts) fails the build if any spotlight anchor is not in the anchor registry. Anchors are declared in a feature's co-located *.ui-anchors.ts file and assembled into STATIC_UI_ANCHORS (exported from src/shared/ui-anchor-registry.ts, backed by the generated ui-anchor-registry.generated.ts) by npm run ui-anchors:reindex — see ui-anchor-colocation-contract.md. This is the same registry that powers GET /ui/snapshot for AI agents — so tour anchors are also AI-pointable, no duplicated wiring.
    • selector — any CSS selector (e.g. "[data-setting-id=\"font-size\"]", ".my-widget"). No registry requirement — this is how a tour highlights an element that has no anchor.
    • text — visible text of the element to highlight (case-insensitive substring; resolves to the leaf-most element that owns the string).
  • reveal (optional, spotlight OR overlay) — bring the target on screen BEFORE spotlighting: { "view"?: "<top-level view>", "settingsSection"?: "<section id>", "open"?: "<modal id>" }. This is what lets one tour walk across many screens and open modals — see Flexible targeting below.
  • placement — 'top' | 'bottom' | 'left' | 'right' | 'auto'. Default 'auto' lets the tooltip pick a side that fits in the viewport.
  • padding — extra space (px) around the cutout hole. Default 8.
  • borderRadius — cutout corner radius (px). Default 12; set to 0 for sharp-cornered targets like the full-width header.
  • phase — must match one of the phaseLabels[].phase strings on the parent tour; drives which dot is highlighted.
  • id (optional) — falls back to the anchor string. Tests sometimes need stable step ids that don't collide with the anchor name.
  • buttonLabel (optional) — overrides the default "Next" button text on this step.
  • interactive (optional) — makes the step hands-on: the tour hides Next and advances only when the user performs the action. Shape: { "advanceOn": { "type": "click" }, "live"?: boolean }. See Hands-on (interactive) steps below. Hands-on steps require an anchor (selector/text steps are read-and-highlight only).

Flexible targeting — highlight anything + cross-screen reveal

Two powers let a tour highlight any element and walk the user across many screens, opening modals along the way. Both reuse the app's existing spotlight + navigation primitives; invariants are locked by app-tour-reveal-targeting-contract.md.

Highlight anything — instead of anchor, a spotlight step can target by raw CSS selector or visible text. These carry no registry requirement, so a tour can point at an element that was never given a data-ui-anchor. Resolution runs through the SAME shared picker as anchors (pickSpotlightTarget), so selector/text targets get the identical 0×0 guard, scroll-into-view, late-mount retry, and centered-fallback safety.

Reveal (cross screens + open modals) — any step (spotlight or overlay) can carry a reveal that fires ONCE on step entry, before the spotlight resolves:

  • "view" — switch to a top-level view (validated ∈ TOP_LEVEL_VIEWS).
  • "settingsSection" — open Settings scrolled to that section.
  • "open" — open a modal/overlay by its reveal-registry id.

After the reveal fires, the tour's existing late-mount resolution waits for the target to appear on the new screen / in the modal, then spotlights it (or shows the centered fallback if it never appears). A build gate (tests/unit/lint/tour-reveal-target-coverage.test.ts) fails if a reveal.view isn't a real view or a reveal.open isn't registered — so a typo can't silently strand the tour on the wrong screen.

{
  "kind": "spotlight",
  "reveal": { "view": "archive" },
  "selector": "[data-ui-anchor=\"archive-search-input\"]",
  "title": "Search your archive",
  "phase": "archive",
  "description": "Jump straight to the archive and find any past session."
}

The openable modal ids live in src/renderer/src/features/app-tour/reveal-targets.ts (currently mind-map, flowchart, bake-off, presentation-staging). There is no universal "open any modal" API in the app, so this registry is the curated, extensible seam — add a new openable surface by adding ONE entry to reveal-targets.ts (id + description) and its idempotent opener to src/renderer/src/features/app-tour/reveal-registry.ts.

Overlay steps

{
  "kind": "overlay",
  "id": "welcome",
  "phase": "welcome",
  "title": "Welcome to Omniscio",
  "buttonLabel": "Show Me Around",
  "icon": "Sparkles",
  "description": [
    { "type": "text", "text": "Your air traffic control tower for AI-powered development." },
    {
      "type": "bullets",
      "items": ["Manage multiple AI sessions", "Review code changes", "Automate workflows"]
    }
  ]
}
  • id — required (overlay steps have no anchor to fall back to).
  • icon (optional) — name of a lucide-react icon (e.g. "Sparkles", "Rocket"). Resolved through resolveTourIcon() in tour-icon-map.ts; unknown names silently fall back to no icon.
  • No anchor / placement / padding / borderRadius — overlay cards are full-screen centered, not anchored to a DOM element.

Description: markdown OR rich block array

The description field accepts either a plain markdown string or a typed-block array. The loader's parseDescription() (in markdown-to-rich.ts) handles both — string descriptions get parsed once at hydration; array descriptions pass through unchanged.

Markdown shortcuts

When you write a string description, these tokens parse into typed blocks:

  • [[shortcut:Esc:Interrupt Claude]] → { type: 'shortcut', keys: 'Esc', label: 'Interrupt Claude' }
  • [[prereq:Create snippets first.]] → { type: 'prereq', text: 'Create snippets first.' }
  • [[tryit:Click + to add a project.]] → { type: 'tryit', text: 'Click + to add a project.' }
  • > a blockquote line → { type: 'tip', text: 'a blockquote line' }
  • - bullet 1\n- bullet 2 → { type: 'bullets', items: ['bullet 1', 'bullet 2'] }
  • 1. step\n2. step → { type: 'ordered', items: ['step', 'step'] }
  • Anything else → { type: 'text', text: '...' }

Rich blocks (array form)

For finer control, write the array directly. Block types from tour-types.ts:

  • TextBlock — { type: 'text', text }
  • BulletBlock — { type: 'bullets', items[] }
  • OrderedListBlock — { type: 'ordered', items[] }
  • TipBlock — { type: 'tip', text } (rendered as an info callout)
  • PrereqBlock — { type: 'prereq', text } (rendered as "Before you start: …")
  • TryItBlock — { type: 'tryit', text } (rendered as a "Try it" call-out)
  • ShortcutBlock — { type: 'shortcut', keys, label } (rendered as a kbd-style chip)

The two forms are equivalent — they hydrate into the same RichDescription shape. Pick whichever reads better for the content.

Scene blocks (demo tour only)

A demo tour needs to show the app in different states (a project added, sessions running, an inbox alert) without those states being real. Each demo step can carry a scene block that mutates a sandboxed demo state when you arrive at the step:

{
  "kind": "spotlight",
  "anchor": "sidebar-sessions-list",
  "title": "Sessions list",
  "scene": {
    "injectProjects": ["demo-project"],
    "injectSessions": ["demo-session-running", "demo-session-needs-you"],
    "activeProjectId": "demo-project",
    "messageOverrides": { "demo-session-running": "demo-running-messages" }
  },
  "description": "..."
}

Scenes are hydrated by resolveSceneRefs() in src/renderer/src/features/onboarding/steps/demo-scenes.ts, which converts string identifiers into the actual demo Project / Session / Message records (referencing the shared DEMO_REGISTRY, so a new demo tour can reuse the existing sample project/sessions). The scene block is consumed by the demo-tour controller (useDemoTour), which drives every tour in DEMO_TOUR_IDS — the onboarding demo, the keyboard demo, and the Managing Your Sessions practice tour. A NON-demo tour can technically include a scene block, but nothing reads it.

How loading works

tour-loader.ts uses Vite's import.meta.glob to eagerly import every tours/*.json file at app boot. Each file is safeParse-d through tourFileSchema. A validation failure throws — the app refuses to start rather than silently dropping a broken tour. The loader then hydrates each TourFile into the runtime TourDefinition shape: strings become parsed RichDescription arrays, icon names become lucide components, scene refs become resolved records.

Tour resolution is cached per process — getAllTours() and getTourById() in tour-steps.ts lazy-build a module-level cache on first call.

Files at a glance

  • tour-types.ts — pure type module. TooltipPlacement, TourId union, the block-type interfaces, OverlayTourStep, SpotlightTourStep, TourDefinition, TourPhaseLabel. No runtime code.
  • tour-schema.ts — Zod schemas matching every type. Single source of truth for what's valid JSON.
  • tour-loader.ts — eager glob + per-file safeParse + hydration to runtime shape.
  • tour-steps.ts — registry (getAllTours, getTourById, ALL_TOURS back-compat array) + SETTINGS_MENU_HIDDEN_TOUR_IDS filter list + the eleven *_TOUR const aliases kept for older imports.
  • markdown-to-rich.ts — string-to-blocks parser (parseDescription).
  • tour-icon-map.ts — resolveTourIcon(name) mapping from icon-name strings to lucide components.
  • tours/*.json — one JSON file per tour slug (the source of truth for content).
  • AppTour.tsx — the runtime overlay component. Reads useAppTourStore for current tour + step index, looks up the tour via getTourById, renders <TourSpotlight> + <TourTooltip> (or <TourOverlayStep>).
  • TourSpotlight.tsx / TourTooltip.tsx — the glow ring + the info card, both SHARED with the AI/CLI highlight so the tour looks identical to POST /ui/highlight. TourSpotlight is the one full glow (3 px ring + halo); a pulse prop distinguishes read-only steps (steady) from action-required ones (pulse). TourTooltip renders through the shared components/ui/SpotlightCard, which owns the position, the fade + scale materialize-in-place entrance (no fly-in from the edge), the glass shell, and the close-X. See ui-anchor-highlight-contract.md + app-tour-x-hit-target-contract.md.
  • useTourTargetRect.ts — measures the spotlighted element via document.querySelector('[data-ui-anchor="<anchor>"]'), polls up to 5 × 30 ms (TOUR_TARGET_RECT_RETRY_COUNT / TOUR_TARGET_RECT_RETRY_INTERVAL_MS) while the target paints in, returns the bounding rect, and reports "missing" after that ~150 ms window — a MutationObserver then keeps watching for a late mount and attaches the instant the element appears.
  • tests/unit/lint/tour-anchor-coverage.test.ts — CI gate that scans every JSON tour file and fails the build if any spotlight anchor is missing from STATIC_UI_ANCHORS.

To author a new tour

  1. Pick a slug (kebab-case) and add it to the TourId union in tour-types.ts.
  2. Create tours/<slug>.json with at least one step. Spotlight steps must reference real data-ui-anchor names — see the next section for adding a new anchor.
  3. Add phase labels for every distinct phase string you use in the steps; the final completion phase typically has an empty label so its dot is hidden.
  4. If the tour should appear in Setup Wizards, the JSON-loaded registry picks it up automatically.
  5. If the tour should be hidden from Setup Wizards (surfaced via a custom entry point instead), add its id to SETTINGS_MENU_HIDDEN_TOUR_IDS in tour-steps.ts.
  6. If the tour suggests a follow-up, add a nextTourSuggestion block.
  7. Verify — node scripts/vitest.js run tests/unit/lint/tour-anchor-coverage.test.ts to catch unregistered anchors, then run the tour interactively from Settings → Setup Wizards.

Authoring a demo (sandboxed) tour — if the tour needs to show actions happening on fake data (like Managing Your Sessions), it is a demo tour, not a plain one: (a) add its id to DEMO_TOUR_IDS in tour-hotkey.ts so useDemoTour snapshots/hides/restores the user's real sessions; (b) drive states with scene blocks referencing DEMO_REGISTRY ids (reuse the existing sample project/sessions to avoid registering new anchors); (c) add it to SETTINGS_MENU_HIDDEN_TOUR_IDS and launch it from a dedicated entry that mounts a <DemoTourRunner tourId="…"> (mirror manageSessionsTourActive in App.tsx / GlobalModals.tsx / the Tours menu) — never the plain startTour path, which has no sandbox behind it. Behavior is locked by tests/unit/features/app-tour/manage-sessions-tour.test.ts.

To add a new tour anchor

If your tour needs to highlight an element that isn't yet registered:

  1. Add data-ui-anchor="<kebab-name>" to the JSX of the element you want to highlight. Names are kebab-case and globally unique.
  2. Declare it in your feature's co-located *.ui-anchors.ts file (beside the component), with { description, kind, when?, related? } — NEVER hand-edit STATIC_UI_ANCHORS / ui-anchor-registry.generated.ts (those are generated and a reindex overwrites any hand edit). Then run npm run ui-anchors:reindex to assemble the co-located entries into the generated registry. See ui-anchor-colocation-contract.md. The same registry powers GET /ui/snapshot for external AI agents — declaring once buys you both the tour spotlight AND AI-discoverability for free.
  3. Run the lint test — tests/unit/lint/tour-anchor-coverage.test.ts will pass as soon as the anchor is in the (reindexed) registry.

The registry-only model means you never query the DOM for a tour anchor — if it's not in the registry, the CI gate fails before merge. No silent "the tour points at nothing" failures.

Related

Related pages: first-time-setup.md covers the first-run setup the onboarding demo runs inside, mindmap.md and kms.md are the gated features whose guided tours launch from their own toolbars rather than the Setup Wizards section, and keyboard-shortcuts.md documents the shortcuts the keyboard demo teaches.

Last verified 2026-10-01