Skip to content

Onboarding & In-Product Help

Fullfinity guides new users with a set of built-in surfaces you declare as data and view properties — no bespoke UI. Everything here works on an empty database (it teaches against the user’s own records), so you never ship fake data to make it useful.

Onboarding steps (the getting-started widget)

Section titled “Onboarding steps (the getting-started widget)”

An ambient corner widget guides users through first actions. It belongs to no view — it sits in the bottom corner on every screen, fused with the help launcher into one entry point — so it never mis-scopes to the wrong app and never costs view real-estate:

  • Collapsed — a compact progress pill (e.g. “8 of 14”) while setup is pending. Once everything visible is done or dismissed it relaxes to a subtle ? help nub.
  • Expanded — a grouped checklist: one collapsible section per app, plus an Account Basics section, each with its own mini-progress and its steps. Every step is a CTA that deep-links into the right app (the click is what establishes app context), so a tenant with many apps is never shown one aggregated wall.

Both section kinds are driven by the same OnboardingStep records — the only difference is whether a step sets app:

  • Account basics — steps with no owning app (company logo, invite a teammate, …).
  • Per-app — steps that set app to the app’s root menu; they group under that app’s section.

Seed steps as data in your module’s data/ folder (they load in production, and are overridable per record by identifier):

- data_type: OnboardingStep
identifier: onboarding_sales_first_quote
apply_once: true
title: Create your first quotation
description: Draft a quote for a customer and send it for approval.
icon: FileText # any lucide icon name
app: sales_menu # root app menu → per-app card. Omit for account-basics.
action: sale_quote_action # the WindowAction the card's CTA opens
sequence: 10
check_model: SaleOrder # completion is checked against this model
# check_filter: Q(...) # optional — see "Completion" below

A step ticks itself off automatically — its completion is re-computed live, so there’s no “mark done” to maintain. Completion is evaluated against the current user’s access rights and active companies.

  • check_model — the model the check runs against (it differs per step: Company, Product, SaleOrder, …).
  • check_filter — an optional Q-expression string, the same dialect as record rules and a view’s visible:. The step is done when at least one matching record exists. Available variables: uid (current user id), cid/company_id (first active company), cids (all active company ids). Examples:
    • Q(image__isnull=False) — “the company has a logo”.
    • ~Q(id=uid) — “another user exists” (invite a teammate).
    • omit it entirely — “any record of check_model exists”.
  • check_method — an optional pkg.module:function returning a boolean, an escape hatch only for completions a Q can’t express.

If the current user can’t read the check_model — they lack access, or the step belongs to an enterprise app whose license has lapsed — the step simply shows as not done rather than erroring, so one unreadable check never breaks the checklist for the whole page.

Steps can be gated with groups (a step only shows to members who can act on it), and dismissals are remembered per user — per step (skip one) or per section (hide a whole app’s or the account-basics list). Progress is done / total across all visible (non-hidden, non-dismissed) steps. When everything visible is done or dismissed the widget shows an “all set” state; a user can bring back anything they skipped from the widget’s “Show all steps again” control.

Give an action a help banner shown inline at the top of its view (once per action, dismissable) — not a per-titlebar icon. Set these on the WindowAction:

- data_type: WindowAction
identifier: sale_order_action
model: SaleOrder
help_icon: ClipboardCheck # lucide icon name (optional)
help_title: Sales Orders # optional heading
help_message: >-
Confirmed quotations become sales orders here. Deliver, invoice and track them
from this view.

The banner only appears when help_message is set, renders on the action’s landing views (list/kanban), and stays hidden for that action once the user dismisses it. A record Form shows the next-step nudge instead, so the two never stack.

Add a hover tooltip to any actionButton or linkButton with hint:

- type: actionButton
name: confirm
properties:
label: Confirm
method: action_confirm
primary: Q(state="Draft")
hint: Click on Confirm to reserve stock and turn this quote into a sales order.

Write the hint as a complete sentence. It renders verbatim wherever it appears — as the button’s hover tooltip and, for a primary action, as the next-step nudge banner. The framework never adds a lead-in or rewrites it, so what you type is exactly what the user reads.

Describe what the action does for the user, not what it changes internally:

hint: Click on Confirm to reserve stock and turn this quote into a sales order. # ✅
hint: Sets state to Confirmed and creates stock moves. # ❌ internals
hint: to reserve stock. # ❌ not a sentence

Naming the button in the sentence (“Click on Confirm to …”) reads well in the nudge, where the button may be scrolled out of view. Keep the name in step with label: if you rename the button — the two sit next to each other in the same block.

On a record, the primary/hero action’s hint is promoted to an inline banner while that action is the expected next move — so a user always knows what to do next. Because primary: can be a Q-expression, the nudge tracks the record’s workflow state automatically. No extra authoring is needed beyond the hint. To tune it:

- type: actionButton
name: confirm
properties:
label: Confirm
primary: Q(state="Draft")
hint: Click on Confirm to reserve stock and turn this quote into a sales order.
# nudge: Everything looks right? Confirm to reserve the stock. # different banner copy
# nudge: false # opt out of the nudge

Set nudge when the banner should read differently from the button’s tooltip — a tooltip is glanced at mid-task, whereas the banner is telling a new user what to do next. Like hint it is a complete sentence and renders verbatim. The button keeps using hint for its tooltip either way.

New users also get a one-time guided tour of the app shell (switch apps, get help, your account). The help launcher — a search over the external help center — is fused into the same corner widget as the getting-started checklist, so there is a single “getting started & help” entry point rather than two floating controls. Both are built-in and need no per-app configuration.