@paf-devs/app-tour
v0.2.1
Published
Accessible guided-tour frontend for the app-tour Django package.
Downloads
16
Readme
App Tour
app-tour is a dual Django and npm package that extracts the guided-tour
capability from Helpdesk for reuse across applications. Django supplies tour
configuration, permissions, templates, migrations, and per-user progress. The
npm package supplies the dependency-free browser engine and styles for apps
that build frontend assets with npm.
It supports multiple tours per app, server-side per-user progress, versioned re-launches, role restrictions, responsive positioning, keyboard focus containment, reduced motion, manual replay, and lifecycle browser events.
Choose the installation mode
- Install the Django package when Django owns authentication, tour configuration, rendering, and server-side progress. This already includes the JavaScript and CSS through Django static files.
- Install both Django and npm packages when the application uses a JavaScript bundler and wants to import the frontend engine and CSS explicitly.
- Install npm only for a custom backend. In this mode, the consuming app must produce the expected markup/data attributes and implement a compatible authenticated state endpoint.
The npm package does not replace the Django package's database model, permissions, templates, migrations, or state API.
Install Django
For local development from this repository:
python -m pip install -e /Users/ryanurms/Documents/app-tourInstall directly from the private GitHub repository:
python -m pip install \
"app-tour @ git+https://github.com/yanigero/[email protected]"Private GitHub installation requires an approved Git credential mechanism on the deployment host. Do not place access tokens in source code, requirements files, Dockerfiles, shell history, or image layers.
To build and install a wheel:
python -m build
python -m pip install dist/app_tour-0.2.1-py3-none-any.whlAdd the app and URL route:
# settings.py
INSTALLED_APPS += ["app_tour"]
# project urls.py
path("guided-tours/", include("app_tour.urls")),Then run:
python manage.py migrate app_tour
python manage.py collectstaticThe standard Django request template context processor must be enabled.
Install npm
The npm package name is @paf-devs/app-tour:
npm install @paf-devs/app-tourFor a private GitHub repository before publishing to the npm registry:
npm install github:yanigero/app-tour#v0.2.1Import the browser engine and styles in the application's frontend entrypoint:
import "@paf-devs/app-tour/tour.css";
import { scanTours, startTour } from "@paf-devs/app-tour";
// Re-scan after AJAX or framework rendering inserts tour markup.
scanTours();
// Optional programmatic replay.
startTour("inventory-dashboard");The Django template tag already references Django static files. Do not also bundle the npm assets on the same page unless the template is overridden to remove those static references; loading the engine twice is unnecessary.
npm exports
| Import | Purpose |
| --- | --- |
| @paf-devs/app-tour | Side-effect initialization plus scanTours() and startTour() |
| @paf-devs/app-tour/tour.js | Raw dependency-free browser engine |
| @paf-devs/app-tour/tour.css | Tour UI styles |
The npm module is safe to import during server-side rendering, but tour
initialization only occurs in a browser with window and document.
Configure a tour
Tours are registered centrally in settings.APP_TOURS. The key must be
stable and unique across the whole PAFILS installation.
APP_TOURS = {
"helpdesk-dashboard": {
"version": 3,
"title": "Helpdesk guided tour",
"autostart": True,
"allow_skip": True,
"steps": [
{
"title": "Welcome to Helpdesk",
"body": "This tour explains the request workflow.",
},
{
"selector": "[data-tour='helpdesk-search']",
"title": "Find a ticket",
"body": "Search across ticket number, requestor, and content.",
},
{
"selector": "[data-tour='helpdesk-new-ticket']",
"title": "Create a request",
"body": "Open the form and describe the issue clearly.",
"next_label": "Finish",
},
],
},
"inventory-dashboard": {
"version": 1,
"permission": "inventory.view_asset",
"steps": [
{
"title": "Inventory",
"body": "Learn the inventory dashboard.",
},
{
"selector": "[data-tour='inventory-filters']",
"title": "Filter assets",
"body": "Narrow the list by unit, status, or custodian.",
},
],
},
}Optional groups restricts a tour to users in at least one named Django group.
Optional permission requires a Django permission. If both are specified, both
must pass. Authorization for the tour never replaces authorization for the
underlying application feature.
Add stable target attributes and render the tour once on the page:
{% load app_tour %}
<button data-app-tour-start="helpdesk-dashboard">Guided tour</button>
<div data-tour="helpdesk-search">...</div>
<div data-tour="helpdesk-new-ticket">...</div>
{% app_tour "helpdesk-dashboard" %}A step without selector is a centered welcome or completion step. A missing
selector is also handled as a centered step, so conditional role-specific
elements do not crash the tour. Prefer separate role tours when the instructions
are materially different.
How it works across apps
Each app owns only:
- its stable tour key and version;
- its instructional text and ordered selectors;
- target attributes in its templates;
- optional group or permission restrictions;
- an optional replay button.
The package owns the shared state model, state endpoint, UI, CSS, JavaScript, focus handling, positioning, and completion rules. A single user can therefore complete Helpdesk while remaining a first-time user in Inventory or RIS.
Increase version only when a changed tour is important enough to offer again.
The next page visit resets that tour to first-time status without deleting its
original first_seen_at. Do not rename a key casually: a new key creates a new
independent history.
For content loaded after page startup (for example a modal inserted with AJAX), insert the tour markup and dispatch:
document.dispatchEvent(new Event("app-tour:scan"));The component emits bubbling events named app-tour:start,
app-tour:step, app-tour:complete, and app-tour:skip. Analytics
code may listen to these without modifying the package. The step event also
lets an app open a practice panel or prepare dynamic content:
document.addEventListener("app-tour:step", function (event) {
if (event.detail.tour !== "helpdesk-dashboard") return;
document.querySelector("#helpdeskPractice").hidden =
!event.detail.step.practice;
});Helpdesk migration
The current Helpdesk implementation should be migrated as follows:
- Configure
helpdesk-dashboardversion 3 using its current dashboard steps. - Replace inline tour CSS, markup, and JavaScript with the template tag.
- Replace current target IDs with stable
data-tourattributes. - Migrate
HelpdeskUserOnboardingrows intoUserTourStatewith the keyhelpdesk-dashboard, preserving timestamps and status. - Configure the ticket thread as a second tour, such as
helpdesk-ticket-requestorandhelpdesk-ticket-admin, because its steps differ by role. - After data and behavior verification, remove the Helpdesk-specific model, endpoint, migration-reset logic, and duplicated tour code.
Do not delete the old model before a data migration and rollback plan exist.
Strengths
- One engine and one progress model for every Django app.
- Progress follows the authenticated user across devices and browsers.
- Independent keys prevent one app's completion from suppressing another app.
- Versions safely re-offer materially revised tours.
- No external JavaScript/CDN dependency.
- Role/permission gates and missing-target tolerance support conditional pages.
- Manual replay, focus return, focus containment, Escape handling, responsive positioning, and reduced-motion support are built in.
- Lifecycle events provide an analytics integration point.
Required work before production
- Complete screen-reader testing on Safari/VoiceOver and Windows/NVDA.
- Test keyboard behavior inside SweetAlert2 and other nested modal frameworks; two simultaneous focus traps can conflict.
- Add a Content Security Policy strategy. The template currently emits a small inline JSON script, which may require a nonce or hash under strict CSP.
- Decide whether "skip" is permanent for a version or whether reminders are required after a cooling-off period.
- Define analytics retention, access, and privacy policy before collecting more detailed step-level telemetry.
- Add translation (
gettext) before multilingual rollout. - Establish content ownership and review: each app owner must verify text, selectors, permissions, version changes, and screenshots during release.
- Add browser tests for desktop, mobile, zoom at 200%, long translated text, dynamically hidden targets, and pages with fixed/sticky headers.
- Add automated orphan-selector checks so template changes cannot silently turn targeted steps into centered steps.
- Decide how multi-page tours should resume. Version 0.1 is page-scoped; it does not navigate between URLs or persist the current step.
- Define anonymous-user behavior. Version 0.1 intentionally requires login and stores no browser-only progress.
- If several tours are rendered on one page, ensure only one autostarts. Application configuration currently owns that coordination.
Security and operational disclosures
- Tour visibility is not feature authorization. Every underlying view and API must continue enforcing its own permissions.
- Step text is rendered from trusted server configuration and assigned through
textContent; do not change the engine to inject untrusted HTML. - State updates require authentication, POST, and Django CSRF protection.
- Tour progress is user activity metadata. Include it in retention, export, deletion, audit, and privacy procedures as applicable.
- The update request is intentionally non-blocking. A network failure does not stop the UI, so state may be offered again later. Monitor endpoint failures.
- Selectors are a public integration contract between app templates and tour
configuration. Prefer purpose-built
data-tourattributes over CSS classes used for styling. - Removing a configured tour does not automatically delete historical state. Retain it for audit or remove it through an approved data-retention process.
Build and verify both packages
python -m django test tests --settings=tests.settings
python -m build
npm run check
npm pack --dry-runPython and npm versions must be released together. A release should use the
same semantic version in pyproject.toml, setup.py, app_tour.__version__,
and package.json.
Publishing to npm is a separate external release action:
npm publishDo not run it until the @paf-devs npm organization/account ownership,
package visibility, release approval, and npm authentication method are
confirmed. A private GitHub repository does not automatically make an npm
package private; publishConfig.access is currently public.
Package status
Version 0.2.1 is an extracted foundation, not a production approval. It must
be integrated into a PAFILS test branch, supplied with the exact existing
Helpdesk step definitions, data-migrated, and subjected to the UAT and
accessibility gates above before replacing the live Helpdesk implementation.
