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:
Packages contribute panels as DATA — a
UiContributionnode withcontext: "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 … groupchapter: communications,accounts,home; anything else lands in Moreorderplace in the chapter labelheading tooltipwhat the assistant says — written in the assistant's voice, first person iconrail icon (a Fluent icon name, an emoji or an SVG URL) address+areathe embedded panel. addressunset ⇒ 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.hrefa 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.The Getting-started steps modules contribute in code (
IChatNudgeSource— Connect your accounts, Find your learning path) are panels too, throughNudgeOnboardingStepSource: 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.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:
- a form bound to the person's own
{you}/_Mail/Preferencesnode (MailPreferences, the platform's content editor — no copy, no save button): sort before I read (sorting, default on), prepare replies before I read (draftReplies, default off — the description says the reply lands as a draft in their own Drafts folder and nothing is sent until they send it), and how far back a copy reaches (copyWindow: last month / three months / year); - the Inbox copies switch right under it, embedded from its own area, so "synced, and for how long" is one panel: the window on the form, the switch below. The copy keeps its ONE switch and its confirmation — a wizard default must never be able to delete a copy switched on elsewhere.
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
- The overview replaces the linear wizard, in place (2026-10-21). Same area, same URL, same
ledger: onboarding's last redirect, the chat line's Start button and every existing deep link keep
working — a link to a panel now opens that panel under the tiles. The linear walk ("way too long and
way too boring") is gone: chapter introductions and the closing panel are no longer rendered (their
words stay in
OnboardingNarrativefor the chapter captions and the welcome), and progress is what the tiles show. The same overview is the Settings app's first tab (GettingStartedAreas.SetupTab, order −10), so/{you}/Settingsopens on it. - Tiles are the step model, rendered as a grid — a catalog once
CatalogApplands. The core catalog app (one shared query bar, a category per chapter, every category a mesh search over a hidden query) is the owner's shape of record for this page too; the tiles switch to it when it is in a sealed core set, with the contributed panels (UiContribution,context: Onboarding) as the hits and a per-viewer item area for the state. - Panels are data, read as the viewer. A package extends the wizard the way it extends the person app and a host app — one contribution lane, one gating rule — and the engine never learns a package's name.
- The assistant speaks only where there is something to say. Chapter introductions are conditional on contributed panels, so a small installation gets a short wizard, not a tour of empty rooms.
- Set up later is final for the chat line, never for the person. The chat line stops offering the step; the tile reads done and offers Change, so nothing a person skipped is lost — it just stops being asked.
- A home panel handles first use IN PLACE. With app home-writes off (
Home:AppSource = Directory) acquiring Home Assistant or Apple Music copies nothing, so an entitled person's first visit finds no personal node to configure. TheirConnectionareas therefore offer Start on the showcase itself — the Store's own install request for the package's declaredstartPaths, filed as the viewer, its verdict streamed into a line under the button — and the reactive grid of the person's copies shows the form the moment it lands. Nothing navigates, so the wizard keeps its position; the cover stays a link to read, never the only way in. A panel closed with Done counts as done, as every panel does — the same form stays in the settings app. - Connect your accounts moved to the communications chapter. What it connects today — Microsoft 365, Teams, iMessage, mail for everyone — is how people reach you; the accounts chapter is for the services a package brings (signing, Google, …).
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) |