Skip to content

New plugin: Extendable Guided Tours (using BUI and NFS) - #11697

Open
christoph-jerolimov wants to merge 28 commits into
backstage:mainfrom
christoph-jerolimov:guided-tour-plugin
Open

christoph-jerolimov wants to merge 28 commits into
backstage:mainfrom
christoph-jerolimov:guided-tour-plugin

Conversation

@christoph-jerolimov

@christoph-jerolimov christoph-jerolimov commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Hey, I just made a Pull Request!

Fixes #279.

Adds a new guided-tours workspace with two plugins for the new frontend system:

  • @backstage-community/plugin-guided-tours: the tour runner, a spotlight overlay with step cards, per-user progress stored in the storage API, automatic start on login or on the first visit of a path, and an overview page at /guided-tours.
  • @backstage-community/plugin-guided-tours-react: the GuidedTourBlueprint, API ref, hooks and target helpers that let any frontend plugin ship a tour without depending on the main plugin.

The main plugin comes with a dev app that showcases tours on the catalog, scaffolder, notifications and user settings pages.

Localization of the tour contents will follow in a later PR. The API for that is not settled yet.

Example

Any plugin ships a tour as an extension built with the GuidedTourBlueprint. Adopters can do the same in their app with a frontend module. When the guided-tours plugin is not installed, the extension stays unattached and nothing breaks.

import { createFrontendModule } from '@backstage/frontend-plugin-api';
import { GuidedTourBlueprint } from '@backstage-community/plugin-guided-tours-react';

const catalogIntroTour = GuidedTourBlueprint.make({
  name: 'intro',
  params: {
    title: 'The software catalog',
    description: 'A one minute walk through the catalog page.',
    // first visit of /catalog; add `exact: false` to start on entity pages too
    autoStart: { path: '/catalog' },
    steps: [
      {
        id: 'welcome',
        title: 'Welcome',
        content: 'The catalog lists **everything** your organization owns.',
      },
      {
        id: 'create',
        target: { selector: 'a[href="/create"]' },
        placement: 'bottom',
        content: 'Scaffold a new project from a template.',
      },
      {
        id: 'first-entity',
        target: { selector: 'table tbody tr' },
        advanceOn: 'click',
        content: 'Click an entity to open its page and finish the tour.',
      },
    ],
  },
});

// in packages/app, or in the `extensions` of your own plugin
export const catalogToursModule = createFrontendModule({
  pluginId: 'catalog',
  extensions: [catalogIntroTour],
});

The tour gets the id catalog/intro. Elements of your own UI are marked with guidedTourTargetProps('my-plugin.element') and referenced by that id as the step target.

Screenshots

Light Dark
Welcome step, centered Welcome step, centered (light) Welcome step, centered (dark)
Anchored step with spotlight Anchored step with spotlight (light) Anchored step with spotlight (dark)
Catalog tour, last step Catalog tour, last step (light) Catalog tour, last step (dark)
Guided tours overview page Guided tours overview page (light) Guided tours overview page (dark)

✔️ Checklist

  • A changeset describing the change and affected packages. (more info)
  • Added or updated documentation
  • Tests for new functionality and regression tests for bug fixes
  • Screenshots attached (for UI changes)
  • All your commits have a Signed-off-by line in the message. (more info)

🤖 Generated with Claude Code

christoph-jerolimov and others added 22 commits October 24, 2026 00:03
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
Proposal, design, tasks and spec for an extension-driven guided tour
system on the new frontend system: a web library plugins depend on
(blueprint, API ref, hook, markdown), a plugin with the api/overlay/page,
per-user progress in user settings, login and route auto-start, and
cross-page steps.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
Two new packages:
- plugins/guided-tour-react: the contract other plugins depend on
  (GuidedTourBlueprint, guidedTourApiRef, useGuidedTour, target helpers,
  minimal markdown renderer built on Backstage UI).
- plugins/guided-tour: api:guided-tour (registry, runner, per-user
  progress in the storage api), the app root overlay (spotlight mask,
  Backstage UI popover anchored with react-aria, route and click-through
  steps) and the /guided-tours page.

App wiring: welcome tour on first login, sidebar entry, data-guided-tour
ids on sidebar items. The example plugin ships its own tour that starts
on the first visit of /example and depends only on the react library.

Verified with unit tests, a Playwright e2e spec and screenshots.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
Centered steps rendered the card straight into the Backstage UI Dialog,
which only pads its DialogBody, and the card had a fixed width narrower
than the dialog. Wrap centered steps and the loading skeleton in
DialogBody, let the card fill its container, and keep the 360px width on
the popover variant only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
Steps with advanceOn: 'click' hid the Next button, which left the
second step of the example intro tour without an obvious way forward.
The button now stays; clicking the highlighted element remains the
second way to advance, and the hint text is kept.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
The component now takes a tourId prop (plus optional label and icon) and
is exported from @internal/backstage-plugin-guided-tour-react so any
plugin can lazy-load it in a PluginHeaderActionBlueprint. The example
plugin reuses it for its intro tour.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
The sidebar steps of the welcome tour now navigate to their pages
(route '/', '/create' and '/settings').

Route matching for steps is prefix based: pages such as the scaffolder
redirect '/create' to '/create/templates', which the exact pathname
comparison treated as "not on the route" and left the step loading
forever. The root route still matches exactly. The same matcher is used
by the resume-on-load rule.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
"Restart the tour" becomes "Restart the guided tour" for the accessible
name and tooltip.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
A step without a route lives on the page the tour reached before it.
The tours page and the resume-on-load rule now use the closest earlier
route (routeForStep) instead of only the current step's own route.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
resetProgress now requires a tour id; resetAllProgress clears every
tour and the onboarded flag. The tours page uses resetAllProgress for
its "Reset all tours" action.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
The standalone dev app now loads the home, catalog, scaffolder,
notifications and user settings plugins next to the guided tour plugin.
Catalog, scaffolder and notifications APIs are replaced by in-memory
mocks (dev/mockApis.tsx, dev/mockData.ts) so no backend is needed.

The dev app gets its own config (dev/app-config.yaml) instead of the
repository's app-config.yaml: a default home page layout, the random
joke widget disabled, and the catalog at its default path.

New in the demo: an "Explore the portal" tour across all those pages,
the RestartTourAction header button, a markdown-heavy first step, and a
home page widget pointing to the tours page.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
Each tour of the dev app now lives in dev/tours/. New tours start on the
first visit of the catalog, create, notifications and settings pages and
spotlight real elements there. The welcome tour of packages/app is
carried over, targeting the default sidebar links by href.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
Remove the "Explore the portal" tour. The catalog tour spotlights the
whole Personal/Company filter block and drops the "More filters" step.
The scaffolder tour spotlights the complete template card. The settings
tour highlights the display name and email, adds a "Logout" step on the
menu button, highlights the whole theme button group and drops the
"More settings" step.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
The "Welcome to the demo" and "Second page tour" tours and the demo and
second pages are gone. The home page is now the landing page of the dev
app, the welcome tour is the login tour, and the header action restarts
it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
The welcome tour (app and dev app) gains steps for the Notifications and
Guided tours sidebar items, drops "Browse and filter", and returns to /
when Done is pressed.

Tours can set `completeRoute`; the overlay navigates there when the last
step is finished with Done or by clicking through.

The progress store now ignores storage notifications for a key while
its own writes are in flight, so a pending write cannot briefly undo a
later one (seen as "Restart" flashing after "Reset all tours").

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
The "Guided tours" step now follows the intro, the notifications step is
titled "Your notifications", and the settings step ends the tour with
"Done takes you back to the start page." Applied to the app and the dev
app; the e2e spec follows the new order.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
Use ">=4.6.0 <4.9.0" like @backstage/ui and the other Backstage
packages, so the guided tour packages share their copy of
@remixicon/react instead of pulling a second one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
Promote the change's spec to openspec/specs/guided-tour/spec.md and move
the change folder to openspec/changes/archive/2026-10-23-guided-tour-plugin.
Task 6.3 (dark theme and screen reader check) stays open and is marked
as such.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
The dev app no longer shows the button on the guided tours page. Instead
the catalog, create, notifications and settings plugin headers get a
button that restarts their own tour (dev/headerActions.tsx).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
Rename the packages to @backstage-community/plugin-guided-tours and
@backstage-community/plugin-guided-tours-react, set their version to
0.0.0, license them under Apache-2.0 and drop the private flag.

The plugin id becomes guided-tours, and the api ref, extension ids,
translation ref, storage bucket and dev tour ids follow it. The plugin
and openspec folders take the plural name too. Adds the workspace README
and the initial changeset and rebuilds the lockfile.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
Restore the leading doc comments that the license header rule replaced,
so the placement type keeps its release tag and the react package its
package documentation. Export the translation ref from an alpha entry
point as other plugins do.

Add react-dom as a dev dependency so jest can load react-router-dom,
polyfill URL.canParse in the test setup for jsdom 20, and drop two
unused dev dependencies that knip reported.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
@backstage-goalie

Copy link
Copy Markdown
Contributor

Important

This PR includes changes that affect public-facing API. Please ensure you are adding/updating documentation for new features or behavior.

Changed Packages

Package Name Package Path Changeset Bump Current Version
@backstage-community/plugin-guided-tours-react workspaces/guided-tours/plugins/guided-tours-react minor v0.0.0
@backstage-community/plugin-guided-tours workspaces/guided-tours/plugins/guided-tours minor v0.0.0

Create the guided tour API ref with the `createApiRef<T>().with()` form
and import the `ScaffolderApi` type of the dev app mocks from
`@backstage/plugin-scaffolder-common`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
@codecov

codecov Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.17073% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.17%. Comparing base (e04b47f) to head (e8a46f3).
⚠️ Report is 23 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##             main   #11697       +/-   ##
===========================================
+ Coverage   75.50%   98.17%   +22.66%     
===========================================
  Files         630       24      -606     
  Lines       17182      656    -16526     
  Branches     4045      162     -3883     
===========================================
- Hits        12973      644    -12329     
+ Misses       4162       12     -4150     
+ Partials       47        0       -47     
Flag Coverage Δ
acr ?
guided-tours 98.17% <98.17%> (?)
keycloak ?
nexus-repository-manager ?
ocm ?
pingidentity ?
quay ?
rbac ?
scaffolder-backend-module-annotator ?
scaffolder-backend-module-kubernetes ?
scaffolder-backend-module-regex ?
scaffolder-backend-module-servicenow ?
scaffolder-relation-processor ?
servicenow ?
tekton ?
topology ?

Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 547ba27...e8a46f3. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

route?: string;
target?:
| string
| {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use to: string or page: string or subpage: string???

christoph-jerolimov and others added 3 commits October 24, 2026 01:56
Document every public type and API member, rename the tour data ref to
`guided-tours.tour` and explain why the extension kind stays singular.
Retitle the workspace README and start the dev app from the workspace
root. Drop the yarnrc the workspace does not need and tidy the gitignore.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
Unit tests now cover the overlay, the step card, the spotlight, the
target hooks, the overview page, the extensions, the react hooks and
helpers and the remaining branches of the api and progress store. Along
the way the target hook stopped leaking the previous step's status into
a new step, which could skip or center a step for a moment.

The workspace gets Playwright tests against the dev app: the welcome
tour across pages, the centered and anchored overlays, the click-through
step, the previous button restoring earlier pages, resume after reload,
Escape and close, the overview page and the header restart action.
CI runs them through the playwrightTests flag.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
The act re-exported by Testing Library is the deprecated one from
react-dom/test-utils, which the CI deprecation check rejects.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
@christoph-jerolimov
christoph-jerolimov marked this pull request as ready for review October 24, 2026 05:35
@christoph-jerolimov christoph-jerolimov changed the title New plugin: guided tours for the new frontend system New plugin: Extendable Guided Tours (using BUI and NFS) Oct 24, 2026
christoph-jerolimov and others added 2 commits October 24, 2026 08:36
The autoStart option now reads `{ path }` to describe the browser location that starts a tour. The step `route` and `completeRoute` keep their names.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>
The autoStart path now matches the location exactly by default. Tours that should also start below the path, for example on entity pages under /catalog, set exact: false.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Christoph Jerolimov <jerolimov+git@redhat.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🔌 Plugin: tour

1 participant