The setup wizard

"i need a perfect onboarding experience for first setup … an onboarding wizard where other plugins can inject a part … a graphically appealing variant where he can click through to configure all his communication channels, all his accounts, to get set up with home entertainment and all is connected" · "create an assistant memex to introduce himself and explain that they will help to manage the life. asking to onboard all the communications … we can intermix setup steps and explanations" — the owner, 2026-10-17.

Right after sign-up a person lands on /area/{you}/GettingStarted — until now a flat list of Getting-started steps (Getting started and Connections). It is now the setup wizard: the assistant, Memex, introduces itself and says what it will do for the person, then walks them chapter by chapter through the panels the installed packages contribute, as tiles on one page, with the assistant's welcome the first time. Every panel has a URL, so a chat line can point straight at one step.

The whole journey — from the public instance's welcome page through the account to this wizard and what the system does afterwards — is written as a process in First setup, end to end.

What the person sees

The setup is the Settings app's front page — its first tab, /{you}/Settings (also /{you}/Settings/Setup), and the bare page onboarding lands on, /area/{you}/GettingStarted (owner, 2026-10-21: "we should probably make this the user settings app", "bring selections with tiles to set up Email, Home Assistant, …", "even when starting a second time, show him what he has already configured, let him configure more", "say that he can always change later"). From the platform's layout controls only (Stack, LayoutGrid, Icon, HeroTitle, Markdown, LayoutArea, Button, Progress, the Card skin):

The first visit Every visit Under the tiles
the assistant's welcome card — two lines, then 🧭 Show me around (the tour, below) and Got it; both mark the welcome done, so the card never comes back one line — What shall I take care of? Pick anything. You can always change it later, right here in your settings. — then one tile per thing the assistant can take care of, in chapter order: the chapter's name, icon and title, the assistant's one line, the state (✓ Done or Not yet) and one button, Set up (or Change when done) the chosen panel, right there: the assistant's line, the package's embedded area (the form, the connection tiles), Open for a target, and Done · Set up later · Close

Nothing is linear. A person sets up what they like, in any order, and the second visit shows what is already configured — a done tile reads ✓ Done and offers Change; nothing is ever asked twice. Set up later marks a step as skipped (the chat line stops offering it) and its tile reads done; Done marks a panel that counts as done when passed; Close marks nothing. A step that is done only once something actually happened (connecting an account) is retired by its source, never by a click. On the bare page one quiet way out (I'll finish this later) leads to the launcher; inside the Settings app there is no page to leave.

The overview fits one screen, and its tiles are the same size (Plugins#4203). The tiles flow in ONE LayoutGrid in tile-flow mode (WithMinTileWidth, core Doc/GUI/LayoutGrid → Tile flow), every tile in a row stretched to the row's height, each tile filling its cell, with the words taking the slack so the state and button line up along the bottom of the row. The chapter is a quiet line INSIDE each tile rather than a caption row of its own: a grid per chapter spent a full row on every caption and left a half-empty last row per chapter, which is what pushed the page past one screen.

One column, one rhythm (the maintainer, 2026-10-30: "layout much better but not good", "and your mail opens below"). The welcome card, the tiles, the opened panel and I'll finish this later share ONE centred column, ColumnWidthPx = 960px wide (full width on a phone), with one 16px gap (GapPx) between its blocks and between the tiles — so every edge lines up and the panel opens directly under the row of tiles, at its width. The tiles share the row evenly: the grid's minimum tile width is COMPUTED from the tile count (TileMinWidth) as the smallest width at which the tile-flow grid makes exactly TilesPerRow tracks in the column — two tiles are two halves, three are thirds, four are two rows of two, more are thirds — and it wraps to one per row on a phone. A fixed minimum (the former 210px in a 1200px grid) left empty tracks beside two tiles and pulled them out of line with the card above. Each tile is a plain FRAME stack around its card: the card skin binds the control's style twice (on the card and on the stack inside it), so the selection ring lives on the frame and is drawn once. An open tile's button reads Close, and its state line says Open below rather than Not yet.

The tutorial bits come the first time and are clicked away ("the tutorial bits we can bring the first time and make the user click away", "let's just use the tutorial plugin to do it and show the user what he can do where — and maybe leave an example"): Show me around opens a Training tour shipped as data by the Training package — Training/Tours/GettingStarted, a Training/Tour node — three steps through the person's OWN settings (the setup tiles, the connected accounts, the chat), each embedded live, with Back to setup as the way out. Its targets are written {viewer} ("target": "{viewer}", "targetArea": "Settings", "targetId": "Setup"), which the player resolves to the signed-in person, so one node fits everyone — and it is the example to copy for a tour of any plugin.

AI does not require Training (MeshWeaver.Plugins#4539). The tour used to live in this package as AI/Tours/GettingStarted, which made Training@^1.3.0 a requirement of AI — so an instance whose type set has no Training/Tour (the control instance: its module set is compiled into the image and it registers no package NodeTypes) refused the whole AI install, NodeType(s) not registered: Training/Tour, on every boot and every repair pass. The tour now ships with Training, and the Show me around button is bound to a children listing of Training/Tours: it appears only where the tour is installed, so an instance without Training shows Got it alone instead of a dead link. AiInstallsOnControlsTypeSetTest installs the real AI package on a mesh with the control instance's type set and holds it to zero failures; its negative control installs the same package with the old requires and fails.

The overview renders only for its owner. A visitor to a profile gets one line.

Where the panels come from

Three sources, all answered live per person and combined by OnboardingService:

  1. Packages contribute panels as DATA — a UiContribution node with context: "Onboarding", shipped in the package's own partition (Mail/Onboarding/Mail, HomeAssistant/Onboarding/HomeAssistant, AppleMusic/Onboarding/AppleMusic). No code in the engine, no release of the engine.

    {
      "$type": "UiContribution",
      "context": "Onboarding",
      "group": "communications",
      "order": 20,
      "label": "Your mail",
      "icon": "Mail",
      "tooltip": "Here you tell me how far I may go with your mail: sort it before you read, draft your replies, keep a copy here.",
      "area": "OnboardingMail"
    }
    
    field the panel's …
    group chapter: communications, accounts, home; anything else lands in More
    order place in the chapter
    label heading
    tooltip what the assistant says — written in the assistant's voice, first person
    icon rail icon (a Fluent icon name, an emoji or an SVG URL)
    address + area the embedded panel. address unset ⇒ the person's own user node — where a panel that writes their settings lives (Mail's). Set ⇒ it must lie inside the contribution's own partition (HomeAssistant/MyHome), exactly like a person-app tab; anything else is dropped.
    href a page to open instead of an embedded panel (an Open button)
    neither area nor href an explanation — the assistant's words alone, for a package that wants to say something before its steps

    The catalog is read as the viewer, so a gated package's panel shows only to someone who holds the package — the same rule the Store's app tabs apply — and a panel shows only while its package is installed here (its Plugins/{package} install record exists; a package uninstalled with its data retained keeps its nodes but loses the record, and the panel with it). The embedded area still runs its own access checks. The panel id is the contribution's full path through the app tabs' prefix-free escape (Mail/Onboarding/Mail → Mail~_Onboarding~_Mail), injective and URL-safe, and it is the key of the per-person ledger.

  2. The Getting-started steps modules contribute in code (IChatNudgeSource — Connect your accounts, Find your learning path) are panels too, through NudgeOnboardingStepSource: one source feeds the chat line and the wizard, nothing is declared twice, and the ledger is shared — a step dismissed in a chat is skipped in the wizard and the other way round. Connect your accounts embeds the connection tiles (OnboardingConnections) in the communications chapter, with the panel's own URL as the return target, so a consent round trip comes back to the wizard; any other step is a panel with an Open button in More.

  3. The assistant's own words — OnboardingNarrative: the welcome, one introduction per well-known chapter, the finish. An introduction appears only when its chapter has at least one panel: the assistant never announces an empty chapter. A contributor cannot shadow these ids or claim the welcome or finish chapter.

A module that needs code to decide whether its panel applies implements IOnboardingStepSource (DI, like IChatNudgeSource) instead of shipping a node. The wizard shows a loading line until every source has answered once (a faulted source counts as answered, with nothing): a person is never handed a welcome-and-finish they could complete while the catalog is still being read.

The per-person state

One ledger, AiSettings.DoneNudges on {you}/_Memex/AiSettings, shared with the chat line (ChatNudgeProgress). A panel named in the URL (/area/{you}/GettingStarted/Mail~_Onboarding~_Mail, the chat line's Start link) opens on arrival; after that the selection is the area's own data (the tiles' buttons write it), so opening and closing a panel navigates nowhere. With no name, no panel is open — the tiles are the page.

The mail panel — the three questions

The owner wanted three things asked on the mail step: whether mails should be pre-categorized, whether it should be synced and for how long, whether replies should be prepared even before reading — and the person told that the reply can be edited before sending. The Mail package's panel (MailOnboardingPanel, area OnboardingMail on the person's user node) answers all of it:

What the preferences drive today: the inbox's triage offers nothing for a person whose sorting is off (OutlookMailboxQueryProvider, read live on every offer; an unreadable preference reads as the default, never as "declined"); Keep a copy starts the backfill at the chosen window (OutlookMailCopy.Enable(owner, windowDays)). draftReplies is recorded and not yet acted on: the pre-drafter — a pass over reply-tagged verdicts that drafts through the Executive Assistant's own DraftReply, into the person's Drafts — is the next piece of Personal triage. The same form is the Mail tab of the person's settings app (/{you}/Settings/Mail), so every answer can be changed later.

What was decided, and why

Where it is implemented

Part Code
The panel model, chapters, source interface src/MeshWeaver.AI/Onboarding/OnboardingStep.cs
The pure rules (arrange, narrative, current, next, progress, URL), the narrative and its words OnboardingSteps.cs — tests OnboardingWizardTests
The contributed-panel source (query as the viewer), the Getting-started adapter, the combiner OnboardingStepSources.cs
The overview (tiles, welcome card, the selected panel) OnboardingWizardArea.cs, registered as GettingStarted (the bare page) and Setup (the Settings tab) on every user node (GettingStartedAreas, SetupTab) — tests OnboardingWizardRenderTest, e2e/onboarding-wizard.spec.ts
The first-visit tour Training/Tours/GettingStarted, shipped by the Training package; the button binds OnboardingWizardArea.TourListingQuery (Training/Tour; {viewer} targets and targetId for a settings tab — Training/Tour/Source/TourContent.cs, TourLogic.ForViewer)
The Onboarding context scripts/check-menu-contexts.py (COMPILED_CONTEXTS)
The mail panel and preferences src/MeshWeaver.Mail.MicrosoftGraph/Inbox/MailOnboardingPanel.cs, MailPreferences.cs; nodes Mail/Onboarding/Mail; tests MailPreferencesTests, MailPreferencesMeshTest, InboxTriageTests
The home panels HomeAssistant/Onboarding/HomeAssistant, AppleMusic/Onboarding/AppleMusic (each embeds the Connection area its person-app tab already shows)