Skip to content

Provide portal onboarding #2890

Description

@DarianGill

Background

PDG users have expressed that the "goodness of pdg takes some work to discover." One way to help users along that journey of discovery would be to provide some form of onboarding. In it's simplest form this could be a modal with instructions and a youtube video, in it's most elegant and extensible, it could involve incorporating an open source tour library like https://driverjs.com/ into metacatui such that portal owners can configure their own tours for any portal.

Potential implementation in metacatui (GPT 5.3 codex):

A good fit for MetacatUI is a small onboarding framework layer that wraps driver.js, with portal-specific behavior defined as config and optional hooks, not hardcoded view logic.

Recommended Architecture

  1. Create a centralized OnboardingManager that owns:
  • When to run a tour
  • Which tour definition applies to the current page/portal
  • Persistence of seen/dismissed/completed status
  • Driver.js lifecycle (start, next, complete, destroy)
  1. Split into clear modules:
  • TourRegistry: resolves applicable tour from context
  • TourStateStore: seen/completed state using persistent storage
  • TourRuntime: thin adapter around driver.js
  • StepResolver: waits for elements, skips/mutates steps if needed
  1. Keep all per-portal differences declarative:
  • Tour metadata, route/view matching, steps, prerequisites, version
  • Optional per-tour hook modules for complex dynamic behavior

Where It Fits In This Codebase

  • Hook after each page render using AppView.js and AppView.js so tours run only when DOM is ready.
  • Instantiate onboarding during app bootstrap in app.js before/around router startup so first-page onboarding can trigger.
  • Put global defaults in AppConfig, which already merges into app state via AppModel.js and AppModel.js.
  • Place theme/portal-specific definitions in theme config files like config.js.
  • For route-aware matching, use patterns that align with portal routing in router.js.

Config Strategy For Very Different Portals
Use layered config precedence:

  1. Global defaults
  2. Theme defaults
  3. Portal-specific overrides keyed by portal id/label
  4. Optional runtime hooks for edge cases

Suggested config shape:

  • onboarding.enabled
  • onboarding.library = driverjs
  • onboarding.autoStart = firstVisit
  • onboarding.tours = array
  • each tour: id, version, priority, match, steps, oncePer, triggers, hooks

Matching inputs can include:

  • route regex
  • current view type
  • portal label/id
  • feature flags
  • user auth state

This gives you one engine, many behaviors.

Persistence
Use existing storage infrastructure instead of raw localStorage:

  • PersistentStorage.js
    Store keys as:
  • onboarding:tourId:version:portalKey:userScope

Recommended userScope:

  • anonymous
  • logged-in subject id when available

This lets you rerun tours when version changes, while keeping old completion state isolated.

Driver.js Integration In This RequireJS Project

  • Add driver.js assets under components and register path in RequireJS near app.js.
  • This repo already maps vendor libs similarly at app.js.
  • Add driver.css into theme/global style pipeline so popovers render correctly.

Behavior Rules That Matter

  • Wait for target selectors with timeout; skip step if configured skipIfMissing.
  • Delay start until transition/render settles (important because views fade/replace).
  • Support manual relaunch from Help/Profile so users can replay tours.
  • Track analytics events for start, step, complete, dismiss.
  • Add frequency controls: once ever, once per version, once per session.

Portal Document vs App Config
You can store onboarding in portal documents, but current portal option serialization is constrained by fixed option names in PortalModel.js and PortalModel.js, with a known custom-option gap at PortalModel.js.
So for phase 1, AppConfig/theme config is lower-risk.
Phase 2 can add portal-editor-managed tours if you decide authoring in portal docs is required.

Estimated Implementation Time

  • MVP (single reusable engine + config + first-visit auto start + 2 to 3 tours): 2 weeks
  • Production-ready (multi-portal config layering, missing-target resilience, replay UI, analytics, tests, docs): 3 to 4 weeks
  • Full portal-editor authoring support (store/edit tours in portal docs): unsure... pending Robyns edits to the mapconfig

Recommended Rollout

  1. Build engine + driver.js wrapper + state store.
  2. Implement two contrasting portals to validate flexibility.
  3. Add replay entry point and analytics.
  4. Expand config coverage and harden with tests.
  5. Optionally add portal-document authoring later.

So, for a portal XML, we’d configure the tour as a JSON payload inside portal <option> elements, then have your onboarding manager read and parse it.

Use something like this inside the top-level <por:portal> (alongside your other <option> entries):

<option>
  <optionName>onboardingEnabled</optionName>
  <optionValue>true</optionValue>
</option>

<option>
  <optionName>onboardingConfig</optionName>
  <optionValue><![CDATA[
{
  "version": "2026.09.01",
  "autoStart": "firstVisit",
  "replayable": true,
  "dismissDays": 30,
  "tours": [
    {
      "id": "portal-home-intro",
      "match": {
        "routeRegex": "^/portals/permafrost/?$"
      },
      "steps": [
        {
          "element": "#portal-header-container",
          "popover": {
            "title": "Welcome to PDG",
            "description": "This is the portal overview and project context."
          }
        },
        {
          "element": ".portal-section-links-container",
          "popover": {
            "title": "Sections",
            "description": "Use these tabs to move between About, Team, Stay Connected, and tools."
          }
        },
        {
          "element": ".portal-section-link[data-section='Data']",
          "popover": {
            "title": "Data",
            "description": "Jump into datasets and filters from here."
          },
          "optional": true
        }
      ]
    },
    {
      "id": "imagery-viewer-tour",
      "match": {
        "routeRegex": "^/portals/permafrost/Imagery-Viewer/?$"
      },
      "steps": [
        {
          "element": ".map-toolbar",
          "popover": {
            "title": "Map Tools",
            "description": "Use layer controls and filters to explore thaw features."
          }
        },
        {
          "element": ".layer-list-toggle",
          "popover": {
            "title": "Layers",
            "description": "Turn data products on/off and compare overlays."
          }
        },
        {
          "element": ".search-input",
          "popover": {
            "title": "Find Places",
            "description": "Search for a location and zoom directly to it."
          },
          "optional": true
        }
      ]
    }
  ]
}
  ]]></optionValue>
</option>

Notes for this codebase:

  • onboardingEnabled works well as a simple boolean portal option.
  • onboardingConfig should be treated as a JSON string and parsed at runtime.
  • Include a version so you can re-show tours when content changes.
  • Use optional: true for selectors that may not exist on all page variants.
  • Route matching should use the real portal paths (/portals/{label}/{section}).

Important implementation caveat:

  • Custom portal options can be parsed fine from XML, but portal editor save/serialize logic usually only persists allowlisted option names. So if you want these values to survive edits in the portal editor, add these option names (onboardingEnabled, onboardingConfig) to the serialized option allowlist in the portal model logic.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Projects

    • Status
      No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions