Skip to main content

Documentation

Guided tours

Declare first-login tours in your manifest; the platform filters each step by the permissions and plan of whoever is viewing, and one shared renderer draws it.

Tours are declared, not coded

A first-login guided tour walks a new user through the few places that matter on day one. On SynaptaGrid you declare it in your app manifest under spec.tours, and one shared renderer in the frontend SDK draws it. Your frontend writes no tour code: it marks the elements a tour points at, mounts the renderer once, and adds a "Show me around" entry so the user can replay it.

The Control Plane resolves each tour for the person asking. Every step can carry a permission gate, a plan-feature gate, or both, and a step the caller does not pass is removed on the server before the response is built. Its text never reaches the browser, so a tour cannot advertise a page the user cannot open or a feature their organization has not bought.

app.manifest.yamlyaml
spec:
  tours:
  - code: welcome
    version: 1
    tier: portal              # portal = your customers; cp = platform operators
    trigger: first_login      # or manual: only opens when replayed
    min_steps: 2              # fewer surviving steps and the tour is not sent at all
    steps:
    - anchor: myapp.nav.projects
      title: Projects hold your work
      body: Every piece of work lives in a project. Create one here to get started.
      requires_permission: projects.read
    - anchor: myapp.nav.reports
      title: Reports
      body: Reports summarise activity across your projects.
      requires_permission: reports.read
      requires_feature: myapp.reports
FieldMeaning
codeUnique within the manifest. The frontend replays a tour by this code.
versionStarts at 1. Raise it when the tour changes enough that everyone should see it again.
tierportal for your customers' users, cp for platform operators.
triggerfirst_login opens automatically once per version; manual only opens on replay.
min_stepsDefault 2. A tour left with fewer steps after filtering is not shown.
steps1 to 12 steps: anchor, title (up to 120 characters), body (up to 600).

Gates

A step is shown only when every gate it declares passes.

  • requires_permission is a permission code, <resource_type>.<action>, checked against the caller's own grants for the organization and app instance they are in. On the cp tier it is checked the same way the operator's console page is.
  • requires_feature is a plan feature code, checked against the organization's current plan. Operators hold no plan, so a cp-tier step with a feature gate is never shown. Leave it off.
  • A code your manifest declares is written bare and checked against the same file when you validate it.
  • A code another app declares is written <that app's metadata.name>:<code>, for example ai-app:assistant.use. The Control Plane checks it when your manifest is published, and refuses the publish if the other app does not declare it. Never qualify your own codes.

Take each gate from what the destination page actually enforces, not from what its menu label suggests. A gate looser than the page's shows the step to people who then cannot open the page.

Anchors

Each step's anchor matches one element in your app's shell:

tsx
<a href="/projects" data-tour-id="myapp.nav.projects">Projects</a>
  • Prefix anchors with your app's name so two apps never share one.
  • Put them on things that are on screen for a brand-new user with no data: navigation and headers, not table rows.
  • A step whose anchor is not on the page is drawn in the middle of the screen with nothing to point at. The text still shows, but test for it rather than rely on it.
  • The Control Plane cannot see your pages, so publishing does not check anchors. Keep a test in your frontend that renders the shell and asserts each anchor resolves to exactly one element.

Mount the renderer once

The frontend SDK draws whatever the Control Plane returns, and records when the user has seen it:

tsx
import { GuidedTourProvider, createPortalToursClient } from "@egav/app-sdk/tours";

<GuidedTourProvider client={createPortalToursClient(api)} appCode="myapp">
  <AppShell />
</GuidedTourProvider>
  • api is your SDK ApiClient pointed at the Control Plane, with the active app instance set.
  • appCode is your manifest's metadata.name.
  • For the operator console, use createCpToursClient(api) with the same provider.

A "Show me around" entry calls useGuidedTours().startTour("welcome"). It is the only way a manual tour opens.

What the user sees

  • A first_login tour opens once per tour version. Finishing or closing it is recorded on the server against that user, so reloading the page or signing in from another browser does not show it again.
  • Raising the tour's version shows it once more to everyone.
  • Keyboard: focus moves to each step's Next button, Esc closes the tour, and focus returns to wherever it was before.
  • If the tour cannot be loaded, the user simply sees no tour. Your app keeps working.

Releasing a change

Tours ship with your manifest. Change a step, publish the manifest with the new version, and the next request serves the new tour. No frontend release is needed unless you moved an anchor.