---
type: plan
title: M2 — tool onboarding, tour contents, and two options with variants
slug: next-milestone-onboarding-and-variants
status: active
owner: adi
created: 2026-09-01
related:
  - /srv/apps/easel/.logs/planning/rebuild-build-plan.md
  - /srv/apps/leaguelaw/notes/designer-notes-pins-homepage.md
summary: >
  Adi's milestone, from rian 2026-09-01. Starts with a full run of the app as
  the client (darren) to build the real backlog, then four asks: a tool
  walkthrough that runs on first arrival, a sidebar contents pane grouping the
  tours, a two-options + toggleable-variants model replacing "six options",
  and a smaller hero for option 2. Written as intent + constraints, not as a
  prescribed implementation — the shape is Adi's call.
---

# M2 — onboarding, contents, and variants

**Owner: Adi.** rian's framing, 2026-09-01. Read `brief.md` and `agents.md`
first; `rebuild-build-plan.md` has the architecture decisions (D1–D10) the
current code follows.

The through-line: **a client should understand the tool before they are asked
to judge the design.** Today they land on a mockup with a comment mode they
have never seen and no orientation. Everything below serves that.

---

## 0. First: run the whole thing as Darren, and write down what's wrong

**rian's ask, and it comes before the four builds above.** Do a real pass
through easel as the client sees it, and turn what you find into the M2
backlog. The four asks above are rian's read of what's missing; this is how
you find the rest — and how you check that what you build actually lands.

**How to get the client's view:**
- **View As darren** (account menu → View As). This is the honest one: it
  renders the app with darren's grants and capabilities, so you see exactly
  his projects, his buttons, his empty states.
- **Know its one limit:** View As is **read-only** for everyone except the
  owner. You can see as darren but not comment, approve, or pick a direction
  as him — those are the very interactions worth testing.
- **So use the QA client account, which already exists.** `easel-qa-client`
  (external, active, granted on easel.bowden.works) was created 2026-09-01.
  Rian has its 48h set-password link — ask him for it; nothing was emailed.
  Sign in as it in a second browser profile and you get the real client
  experience *including writing*.
- **Set up your own test board — you do not need rian for this.** As an app
  admin you can create a project in the UI, add screens and options, and
  **Upload folder** a bundle straight from `leaguelaw/prototyping/` (take the
  page plus its `img/` directory). Then grant `easel-qa-client` `member` on
  that project from the access matrix. Keep test pins off the real `league`
  board — rian is going to present it to darren.
- **Do it at phone width too.** Every client-reachable page has a phone floor,
  and darren is as likely to open this on a phone as not.

**Walk the actual journey, not the feature list.** Land cold on the board
with no explanation → figure out what you are looking at → run the
walkthrough → try to leave a piece of feedback → get a reply → pick a
direction. At every step ask rian's two questions from `brief.md`: *did that
cost the client time?* and *would that produce feedback specific enough to act
on?*

**Things already suspected to be weak — confirm or dismiss:**
- There is no orientation at all on arrival (that is ask 1).
- Comment mode is a mode you must *know* to switch into; nothing invites a
  first comment.
- "Choose this direction" is a button with no confirmation and no undo for
  the client (only the team can clear a selection) — is that right?
- The bell is polling-only and easel sends **no email** (see below), so a
  client who closes the tab learns nothing until they come back.
- Empty states: a project with no options, an option with no walkthrough.

**Write the findings into `.logs/handoff.md`** as a numbered list with a
severity call on each, and let that shape M2's order. Findings that are
substrate friction (the framework, gateway, standards) go to
`.logs/v8-feedback.md` instead — that loop is what turns this project's pain
into a fix for the next one.

---

## 1. A tool walkthrough on first arrival

**Ask:** the first thing a user sees when they reach a board is a walkthrough
of *how to use the tool* — how to comment, why some photos are blurry, what
Play vs Comment mode means, how to pick a direction.

**Why it is separate from what exists.** The walkthrough today is a *design*
walkthrough: it explains the client's own homepage. This is a second kind —
about easel itself — and it is the same engine pointed at different content.
Adi's own designer notes already anticipated the overlap: his pin 2 ("The
notes have an off switch") and pin 3 ("Why is everything blurry?") are
tool-explanation beats that had to be smuggled into the design tour. Those
two belong here instead, which shortens the design tour.

**Constraints:**
- **It must not be a wall in front of the work.** rian's client-effort rule:
  no extra steps, no homework. Skippable, and re-runnable later from the UI.
- **It should run once per person per board**, not every visit — the
  hartlingowners tour's `tour_seen_version` idea is the precedent worth
  copying (`/srv/apps/hartlingowners/main/accounts/tour.py`): a version
  integer stored per user, bumped when the tool changes enough to re-teach.
- Steps that talk about *the tool* target easel's own chrome (the mode
  toggle, the option list), not selectors inside the mockup — so this is
  probably a different target vocabulary from `walkthrough_steps.target_
  selector`, which resolves inside the iframe. Deciding whether that is a new
  target type on the same table or a separate table is Adi's call; note it in
  the handoff either way.

**Content source:** Adi's Set A pins 2 and 3 verbatim, plus commenting and
direction-picking. Keep it to about four beats.

---

## 2. A sidebar contents pane

**Ask:** a sidebar table of contents with sections — the tool walkthrough,
plus logical groupings of tours.

**What this implies structurally:** tours become a *named, grouped* thing
rather than one flat list per option. A grouping might be "How to use this",
"The homepage", "What changes between options". The current model has one
implicit tour per option (`walkthrough_steps` ordered by `step_order`) with
no name and no grouping — so this needs a section/group concept above steps.

**Constraints:**
- The client should be able to see **what they have and have not been
  through** — the contents pane is also a progress indicator. Approvals
  already exist per step (`step_approvals`); a section is "done" when its
  required beats are approved.
- Works at phone width (the floor applies to every client-reachable surface).
- Don't lose the deep link: `?pin=` and the notification URLs point into
  specific places today.

---

## 3. Two options, plus variants

**Ask:** only two design options. Within an option, **variants** the client
can toggle or switch — the hexagon decorations, the different header
configs — rather than a separate option per combination.

**Why this is the right correction.** Adi's own plan had six homepage options
= three openings × two brand intensities, and his notes already flag the
problem: in `v1 v2 v3 v4 v5 v6` order "the 2→3 and 4→5 steps change the
opening *and* the brand at once, which is the hardest kind of comparison to
hold in your head." Making brand intensity a **toggle inside an option**
instead of a separate option is exactly the fix — it collapses six into two
and makes every comparison one-variable, which is the whole point of the
direction gate.

**Constraints and open questions (Adi's call, but decide deliberately):**
- **How a variant is expressed in the bundle.** Options: a CSS class toggled
  on `<html>` via the bridge; separate stylesheets swapped; or separate HTML
  files per combination presented as one option. The first keeps one bundle
  and makes toggling instant, but requires the mockups to be authored with
  that in mind — worth agreeing with whoever builds the concepts.
- **Does a variant choice count as part of the direction?** If the client
  picks "Option 1 + quiet brand + header B", the selection is no longer just
  `screens.selected_option_id`. Rollup's `done` state depends on this, so
  decide before building: probably a `selected_variants` JSON alongside the
  option id.
- **Pins are per option today** (`subject_type="pin"`, one thread per pin,
  anchored x/y). If variants change layout, a pin's anchor may drift. Decide
  whether pins are per-option (simplest, and Adi's existing rule) or
  per-option-per-variant.

---

## 4. Option 2: a smaller hero

**Ask:** option 2 has a smaller hero header, closer to League & Williams'
existing site.

This is design work in `leaguelaw`, not easel code. The current seeded
options are The Direct Answer (no hero), The Welcome Desk (full-screen hero +
form), The Bold Welcome (hero + H1). Under the new two-option model the
likely shape is: **option 1 = The Direct Answer** (the compliance reference
rian greenlit 2026-08-26), **option 2 = a reduced-height hero** — the
existing site's proportions rather than the full-screen treatment.

The live replica measured for exactly this comparison is in
`/srv/apps/leaguelaw/prototyping/` (the replica family the curation notes
call "live replicas + wireframe fidelity calibrations"). Re-seeding easel
afterwards is `docker exec easel-app python -m app.seed_league` — but see
"Re-seeding" below.

---

## Working notes for this milestone

**View As is available to you.** `adi` holds `admin` with `accounts.view_as`
and `all_instances`, and `darren` is a valid target (verified 2026-09-01).
Use it constantly on this milestone — every one of these asks is about the
*client's* first experience, and View As is the only way to see it without
being darren. It is read-only for non-owners by design: you will see exactly
what he sees and cannot write as him.

**Re-seeding.** `app/seed_league.py` is idempotent but **only fills gaps** —
it will not rewrite an option, bundle, or walkthrough that already exists. To
re-seed changed content, delete the relevant rows first (that is what the
2026-09-01 fixes did: `DELETE FROM mockup_files; DELETE FROM walkthrough_
steps;` then re-run). If M2 changes the content model, consider giving the
seed an explicit `--replace` path rather than hand-deleting.

**Email: easel sends none.** Verified 2026-09-01 — no Resend, no SMTP, no
mail wiring of any kind in easel. The `notifications.delivered_at` column
exists as the Interaction Standard's §7 delivery seam and is never written.
The gateway *does* hold a Resend key and has a send endpoint, but it is
owner/hosting-peer only and resolves recipients from its own allowlist, so
easel cannot use it as-is — connecting it is a decision for rian, not a
config change. Until then the bell is the only channel, which means **a
client only learns something is waiting by opening easel.** That is the
review app's known weakness, and it is worth weighing in your Darren pass.

**Container commands need rian, but less than you'd think.** `docker exec` is
owner-only until `srv-gw exec` exists (filed in `/srv/.logs/ideas.md`), and
`srv-gw db-query` cannot help — it targets the shared MariaDB and easel is
Postgres (confirmed: easel's `.project.json` has no `db_name`). But this only
blocks **re-seeding the `league` board specifically**. Everything else —
creating projects, screens, options, uploading bundles, authoring
walkthroughs, granting the QA account — is doable in the UI as an admin. Build
your test board that way and you are unblocked.

**Version control exists now.** `/srv/apps/easel` is a git repo as of
2026-09-01 (baseline commit at v0.4.2). Commit as you go. `.gitignore`
excludes secrets (`.app.env`, `.bw-auth.env`), runtime state (`data/`), build
products, and large media in `notes/` — **check it before adding anything that
could carry a credential.** No remote is configured; it is local history, not
a backup, and `/srv/apps` is not otherwise backed up.

**A browser is on you, not rian.** The visual pass is the highest-value check
on this milestone — last session's live review found three defects in minutes
that 94 green tests had missed (missing bundle images, the walkthrough
spotlight on the wrong element, the right edge clipped). Connect the Claude in
Chrome extension to your own browser and drive the app yourself; you have a BW
account and a site grant, so nothing is gated. Do not skip it.

**Two traps this codebase already hit** (full detail in the 2026-09-01 diary
and caddie's runbook §5):
- A mockup bundle is a **directory**, not a file. Relative `img/...` must be
  uploaded with it (the Upload folder button does this).
- Walkthrough targets are **CSS selectors resolved live**, and the bridge
  takes the first *visible* match. Percentage rects are the fallback only.

**Don't touch without a reason:** the `/m/` serving route's CSP sandbox
headers, the `allow-scripts`-only iframe sandbox, and the path-token auth.
That trio is what keeps client-authored HTML from running as easel; adding a
sandbox token to make a mockup behave is a stop-and-ask, not a judgment call.
