Spec: F52 — Onboarding & Activation Flow
Status: shipped
1. Problem & CEO Job
Job: Decide fast — but not before the CEO reaches a first Brief. The current register → create-org → empty-Today flow has no guided path, demo data, first-Brief promise, or activation metric; a CEO who signs up at 8 PM and sees no tasks or team can simply leave.
Strategy v2 (2026-06-14) defines onboarding as the F70 wedge, not an industry-picker wizard. The target owner runs on Zalo and Excel. Conversion is pasting their own mess — a Zalo thread, Excel export, or screenshot — and receiving a useful, evidence-cited Boss Brief about their company in under 10 minutes, with no setup or blank workspace. Demo data is only a fallback. Activation is a Brief that identifies a real commitment, risk, or decision from that input, not industry-templated filler; before the paste→brief loop is friction, after it is retention.
The industry dropdown → invite team → demo Brief path below is the as-built v1, including activation as the first demo Brief viewed. It remains documented behavior, not the target: the F70 own-data-first realignment is a §11 implementation-audit gap, not a shipped change.
2. User Stories & Acceptance Criteria
Guided Onboarding Wizard
- As a new CEO, I want a 3-step onboarding wizard after organization creation, so that Lumi is configured for my business before I see an empty dashboard.
- Step 1 — “What kind of business?”: select industry from dropdown (Manufacturing, Retail, Services, Tech/SaaS, Construction, Trading/Import-Export, Education, Healthcare, Other). Selection pre-loads demo teams, sample project, and pulse templates.
- Step 2 — “Invite your team”: input email addresses (comma-separated) or skip. Emails queued for invitation on submit. Shows “3 invitations will be sent.”
- Step 3 — “Your first brief”: shows a loading animation (“Lumi is preparing your morning brief…”) while the backend generates a demo brief with sample data for the selected industry. ~5 seconds.
- Onboarding can be skipped entirely with one “Skip, take me to Lumi” link at the bottom. Skippers still get demo data (just silently generated).
Demo Data Scaffolding
- As a new CEO, I want to see a realistic brief immediately, so that I understand what Lumi does without inviting my team or creating tasks.
- Demo data includes: 1 sample project (industry-appropriate), 3–5 tasks (some overdue, some due today), 2–3 demo commitments, 1–2 demo risks, 1 demo decision. (Confirmed 2026-07-02:
demo_data.pytemplates verified — manufacturing template: 1 project, 3 tasks withdue_days_from_nowof -1/0/3 covering overdue+today+future, 2 commitments, 1 risk, 1 decision.generate_demo_data()creates all items withis_demo=True. §11 Rows 7+8 ✅.) - Demo data is clearly marked: “Demo” badge on all demo-generated items, with a dismissible banner: “This is sample data to show you how Lumi works. It’ll disappear when your team adds real data.”
- CEO can dismiss all demo data with one click: “Remove sample data” in the banner. This deletes all demo items.
- Demo data is generated server-side as a Celery task on org creation (no blocking the registration flow).
- Demo data includes: 1 sample project (industry-appropriate), 3–5 tasks (some overdue, some due today), 2–3 demo commitments, 1–2 demo risks, 1 demo decision. (Confirmed 2026-07-02:
First-Day Brief
- As a new CEO, I want a brief that’s ready within 60 seconds of signup, so that I don’t leave before seeing the product’s main value.
- Brief generation for new orgs is immediate (triggered on org creation, not waiting for the 15-minute beat).
- Demo brief shows: decisions pending (2–3), overdue commitments (2–3), risks (1–2), with realistic industry-specific language.
- CEO can interact with demo brief items: assign, dismiss, comment. These work on real database objects — there’s no “demo mode” that breaks functionality.
Activation Tracking
- As a product team, I want to know which new signups reach activation, so that I can measure onboarding effectiveness.
- Activation event: “Brief viewed” — tracked when a user opens the Brief page for the first time and there’s at least one item.
- Tracked via PostHog (already integrated):
onboarding_step_completedevents for each wizard step,activation_completedevent on first brief view,demo_data_dismissedon banner dismissal.- Backend instrumentation (F85): the activation event this story referenced as a PostHog placeholder is now the self-hosted
ActivationEventledger — first brief view emitsbrief_viewedviametrics.emit()(F85 §5.1). F85’sActivationEventis the source of truth for the activation/retention gates; PostHog, if used, is an optional downstream mirror.
- Backend instrumentation (F85): the activation event this story referenced as a PostHog placeholder is now the self-hosted
- Dashboard metric (F43 Admin HQ): signup → activation rate, average time to activation, % who skip onboarding.
- No PII in activation events — industry selection is fine (aggregate), but never track individual data.
Welcome Bot Message
- As a new CEO, I want a welcome message from Lumi in chat, so that I know the AI assistant is active and how to use it.
- On first visit to
/hub/chat, Lumi sends an automated welcome message: “Chào mừng đến với Lumi AI Workspace! Tôi là trợ lý AI của bạn. Bạn có thể hỏi tôi về: tình hình công ty, quyết định đang chờ, rủi ro khách hàng, hoặc cam kết trễ hạn. Hãy thử hỏi: ‘Hôm nay tôi cần quyết gì?’” (localized per user language). - Welcome message appears only once (per org). Flag stored on Organization:
welcome_message_sent.
- On first visit to
Quick Actions on Empty States
- As a new CEO landing on Today with no team members, I want the empty state to drive the next action, so that I don’t stare at a blank page.
- Empty Today: “Chưa có gì cần chú ý hôm nay.” + CTA “Xem Brief mẫu” → navigates to Brief.
- Empty Brief (shouldn’t happen with demo data, but just in case): “Brief của bạn đang được chuẩn bị. Sẽ sẵn sàng trong ít phút.” + “Mời team để có dữ liệu thật” → navigates to Teams.
- Empty Projects: “Dự án đầu tiên của bạn” → create project form inline.
- Empty Tasks: “Tạo task đầu tiên” → create task form inline.
3. Scope Boundaries
- In:
- 3-step onboarding wizard (industry → invite team → demo brief).
- Demo data generation (industry-specific, dismissible, non-blocking).
- Immediate brief generation on org creation.
- Activation tracking (PostHog events + Admin HQ metric).
- Welcome bot message on first chat visit.
- Quick-action empty states.
- Out:
- A/B testing onboarding flows — track metrics first, experiment later.
- Personalized onboarding paths (different flows for different roles) — CEO-focused flow only.
- Video tutorials or interactive product tours — text + demo data is faster to ship.
- Post-activation email drip campaign (covered by F35 notification spec if needed).
- Industry-specific pulse questions (v1 uses standard 6 questions for all industries).
- Invitation scheduling or bulk CSV import — comma-separated email input only.
4. Data Model
4.1 Organization Model Changes
# Organization — new fields
industry = models.CharField(max_length=50, blank=True, choices=[
("manufacturing", "Manufacturing"),
("retail", "Retail"),
("services", "Services"),
("tech_saas", "Technology / SaaS"),
("construction", "Construction"),
("trading", "Trading / Import-Export"),
("education", "Education"),
("healthcare", "Healthcare"),
("other", "Other"),
])
onboarding_completed = models.BooleanField(default=False)
demo_data_generated = models.BooleanField(default=False)
welcome_message_sent = models.BooleanField(default=False)
4.2 Demo Data Model (Temporary)
Demo data uses the same real models (Project, Task, Commitment, Risk, DecisionRequest, Brief, BriefItem) — no separate demo table. Distinction is via a flag:
# Task, Commitment, Risk, DecisionRequest — new field
is_demo = models.BooleanField(default=False)
On “Remove sample data,” all items with is_demo=True in the org are soft-deleted (or hard-deleted if no team interactions yet).
4.3 Demo Data Templates
Stored as a Python dict in backend/apps/onboarding/demo_data.py — one template per industry:
DEMO_TEMPLATES = {
"manufacturing": {
"project": "Triển khai dây chuyền sản xuất mới",
"tasks": [
{"title": "Kiểm tra thiết bị dây chuyền", "priority": "high", "due_days_from_now": -1},
{"title": "Đào tạo công nhân vận hành", "priority": "medium", "due_days_from_now": 3},
{"title": "Chạy thử nghiệm batch đầu tiên", "priority": "critical", "due_days_from_now": 0},
],
"commitments": [
{"title": "Giao 500 đơn vị sản phẩm cho đối tác ABC", "status": "pending"},
{"title": "Hoàn thành báo cáo chất lượng ISO", "status": "overdue"},
],
"risks": [
{"title": "Nguyên liệu từ nhà cung cấp XYZ chậm 5 ngày", "severity": "high"},
],
"decisions": [
{"title": "Phê duyệt tăng ngân sách nguyên liệu 15% cho Q3"},
],
},
# ... one template per industry
}
4.4 No New Models Beyond What’s Listed
No separate OnboardingStep or DemoData model. Simplicity.
5. API Contract
| Method | Path | Role | Description |
|---|---|---|---|
| GET | /api/organizations/current/onboarding/ | any member | Returns onboarding status: {onboarding_completed, demo_data_generated, industry} |
| PATCH | /api/organizations/current/onboarding/ | any member | Saves industry selection. Body: {industry: str}. If {skipped: bool} included, also sets onboarding_completed=True. |
| POST | /api/organizations/current/onboarding/complete/ | owner | Marks onboarding_completed=True. Body: {industry: str, skip: bool} (optional). |
| POST | /api/organizations/current/demo-data/ | owner | Triggers demo data generation synchronously. Idempotent — returns existing if already generated. |
| DELETE | /api/organizations/current/demo-data/ | owner | Deletes all demo data (is_demo=True items) from Task, Commitment, Risk, DecisionRequest. Returns {demo_data_generated: false, deleted: {tasks, commitments, risks, decisions}}. |
The complete_onboarding endpoint also auto-triggers demo data generation after org creation via generate_demo_data().
6. UI / UX
6.1 Onboarding Wizard
┌─ Welcome to Lumi AI Workspace ──────────────────────────────────┐
│ │
│ Step 1 of 3 │
│ │
│ What kind of business do you run? │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Select your industry... ▾ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ We'll set up sample data that matches your world. │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Continue → │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ Skip, take me to Lumi │
└──────────────────────────────────────────────────────────────────┘
6.2 Post-Onboarding: Today with Demo Banner
┌─ Today ─────────────────────────────────────────────────────────┐
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 📋 This is sample data showing what Lumi can do. │ │
│ │ When your team adds real data, it'll replace these items. │ │
│ │ [Remove sample data] [Dismiss] │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ 3 Decisions · 2 Overdue · 1 Risk │
│ │
│ ⚠️ Phê duyệt tăng ngân sách... [Approve] [Snooze] │
│ [Demo] │
│ │
│ 📋 Hoàn thành báo cáo chất lượng ISO [Resolve] [Snooze] │
│ [Demo] · 3 days overdue │
│ │
│ 🔴 Nguyên liệu XYZ chậm 5 ngày [Acknowledge] │
│ [Demo] · High severity │
└──────────────────────────────────────────────────────────────────┘
6.3 i18n
New namespace: onboarding. Keys:
onboarding.step1_title,onboarding.step1_subtitle,onboarding.industry_labelonboarding.step2_title,onboarding.step2_subtitle,onboarding.step2_skiponboarding.step3_title,onboarding.step3_loading,onboarding.step3_doneonboarding.skip,onboarding.back,onboarding.continueonboarding.industry_manufacturing,onboarding.industry_retail, … (one per choice)onboarding.demo_banner,onboarding.demo_banner_remove,onboarding.demo_banner_dismissonboarding.welcome_chat_message
Both en and vi updated. Industry labels localized.
7. Test Plan
Coverage note (2026-06-14): Onboarding tests live in
backend/tests/test_onboarding.pywith classesTestOrganizationFields,TestIsDemo,TestDemoDataTemplates,TestDemoDataGeneration,TestOnboardingProgressEndpoint,TestCompleteOnboardingEndpoint,TestGenerateDemoDataEndpoint,TestDismissDemoDataEndpoint. Tenant isolation is intest_multitenancy.py. Rows below use original per-case naming; the file is authoritative.
| # | Case | Type | Where |
|---|---|---|---|
| 1 | Onboarding wizard: select industry → invite team → demo brief generated | e2e | describe("Onboarding") |
| 2 | Onboarding wizard: “Skip, take me to Lumi” skips wizard but still generates demo data | e2e | same block |
| 3 | Onboarding wizard: back button preserves selected industry | e2e | same block |
| 4 | Demo data appears on Today with [Demo] badges | e2e | same block |
| 5 | Dismiss demo data banner removes all demo items | e2e | same block |
| 6 | ”Remove sample data” deletes is_demo=True items only; real data preserved | backend | tests/test_onboarding.py |
| 7 | Demo brief has industry-appropriate content per template | backend | tests/test_onboarding.py::TestDemoDataTemplates |
| 8 | Demo data generation idempotent — second call returns existing data | backend | tests/test_onboarding.py |
| 9 | Activation event fires on first brief view (PostHog) | unit | frontend PostHog event test |
| 10 | Welcome bot message appears only once per org | backend | tests/test_onboarding.py |
| 11 | Welcome bot message localized per user language | backend | tests/test_onboarding.py |
| 12 | Quick-action empty states render correct CTAs — Today, Brief, Projects, Tasks | e2e | describe("Empty States") |
| 13 | Skip onboarding still generates demo data (industry=“other”) | backend | tests/test_onboarding.py |
| 14 | Invited member joining existing org skips founder-style onboarding | backend | tests/test_onboarding.py |
| 15 | POST /api/organizations/current/onboarding/complete/ sets onboarding_completed=True | backend | tests/test_onboarding.py |
| 16 | DELETE /api/organizations/current/demo-data/ removes only is_demo=True items | backend | tests/test_onboarding.py |
| 17 | Cross-tenant: org A’s demo data never visible to org B | backend | tests/test_multitenancy.py |
| 18 | Pilot banner on Today shows correct days remaining | e2e | describe("Today") |
| 19 | Demo data items respect role permissions (viewer cannot delete) | backend | tests/test_onboarding.py |
| 20 | GET /api/organizations/current/onboarding/ returns status for any member | backend | tests/test_onboarding.py::TestOnboardingProgressEndpoint::test_get_onboarding_returns_status |
| 21 | PATCH /api/organizations/current/onboarding/ saves industry | backend | tests/test_onboarding.py::TestOnboardingProgressEndpoint::test_patch_onboarding_saves_industry |
| 22 | PATCH /api/organizations/current/onboarding/ with skipped: true marks completed | backend | tests/test_onboarding.py::TestOnboardingProgressEndpoint::test_patch_onboarding_saves_skipped |
| 23 | POST /onboarding/complete/ with industry in body saves industry + completes | backend | tests/test_onboarding.py::TestCompleteOnboardingEndpoint::test_complete_onboarding_with_industry |
| 24 | Cross-tenant: org B cannot patch org A’s onboarding state | backend | tests/test_onboarding.py::TestOnboardingProgressEndpoint::test_cross_org_patch_blocked |
| 25 | PATCH response includes updated onboarding state | backend | tests/test_onboarding.py::TestOnboardingProgressEndpoint::test_patch_onboarding_returns_updated_status |
| 26 | Organization field defaults — onboarding_completed, demo_data_generated, welcome_message_sent | backend | tests/test_onboarding.py::TestOrganizationFields |
7.2 As-Built E2E (T3-C R11)
| # | Case | Type | Where |
|---|---|---|---|
| E1 | DemoBanner renders on Today page for demo-seeded orgs | e2e | describe("Onboarding (F52)") in lumi.spec.ts |
| E2 | Today empty state shows “View sample brief” CTA when no data | e2e | describe("Onboarding (F52)") in lumi.spec.ts |
| E3 | Onboarding page is accessible and renders step content | e2e | describe("Onboarding (F52)") in lumi.spec.ts |
| E4 | GET /api/organizations/current/onboarding/ returns status object | e2e | describe("Onboarding (F52)") in lumi.spec.ts |
| 27 | is_demo field defaults on Task, Commitment, Risk, DecisionRequest | backend | tests/test_onboarding.py::TestIsDemo |
8. Current Behavior
The shipped v1 path is register → create/join org → web onboarding wizard → assistant-first destination. The wizard persists industry/invitation/completion state and the onboarding API can generate and dismiss organization-scoped demo objects. The repository has unit and API proof plus registered browser proof IDs; browser execution, native post-auth behavior, and deployed activation metrics remain explicit gaps.
The v1 wizard is not the product’s target activation wedge. F70 owns the future paste-your-own-chaos to useful own-data brief path; F52 does not claim that path is shipped. Welcome-message delivery, asynchronous Celery generation, and the activation funnel also remain open according to §11.
9. Error States
| # | Scenario | User-visible | Internal | Log | Retry |
|---|---|---|---|---|---|
| 1 | Onboarding step API fails (POST /onboarding/complete/) | Toast: “Could not save onboarding progress. Try again.” | Server error logged | ERROR “onboarding_complete_error” | User retries via button |
| 2 | Demo data generation Celery task fails | Banner on Today: “Demo data couldn’t be generated. [Try again]“ | Task failure logged | ERROR “demo_data_generation_failed” | Manual “Try again” button |
| 3 | Demo data deletion fails mid-way (partial delete) | Toast: “Some demo data could not be removed. [Retry]“ | Partial delete logged; remaining is_demo=True items retried | ERROR “demo_data_partial_delete” | Manual retry |
| 4 | Welcome bot message send fails (chat service unavailable) | Silently skipped — no user impact | Logged but no user retry needed | WARNING “welcome_message_failed” | Retry on next chat visit |
| 5 | Non-owner calls onboarding endpoints | 403, redirect to Today | Logged for audit | INFO “onboarding_permission_denied” | n/a |
| 6 | Organization not found in API call | 404 | 404 response, logged | ERROR “onboarding_org_not_found” | n/a |
| 7 | Demo data already generated, POST called again | 200 with existing data (idempotent) | No-op, return existing state | DEBUG “demo_data_already_exists” | n/a |
| 8 | Invalid industry value in POST body | 400 with field validation | Validation error | WARNING “onboarding_invalid_industry” | User corrects form |
| 9 | Demo data and onboarding API called for org that was never onboarded | 400: “Organization not in onboarding state” | Check fails | WARNING “onboarding_not_in_progress” | n/a |
10. Diagrams & Docs Touched
frontend/DIAGRAMS.mdactivation funnelbackend/DIAGRAMS.mdonboarding state or event model if implemented- F24 and F30 specs where onboarding hands off into billing or mobile
11. Implementation Audit
Contract Graph reconciliation (2026-07-15)
The shipped evidence is narrower than the original Status: shipped label. The backend onboarding
progress/completion and demo-data endpoints, deterministic demo templates, frontend wizard tests,
and the registered Playwright journey are now contract-owned. The current behavior section above is
stale where it says that no wizard or demo data exists; executable code and tests are the authority
for that v1 surface. The strategy section is still authoritative about the unresolved product
direction: F70 paste-your-own-chaos and a useful own-data brief are not delivered by this wizard.
Synchronous generation, activation/welcome instrumentation, browser execution, native runtime, live
provider/PostHog, Redis, Sentry, and production metrics remain explicit gaps. No proof is inferred
from collection alone.
Edge Cases Catalog (formerly §9, 2026-06-14; 11 items): invited member skips founder wizard, activation event fires once only, pre-selected paid plan copy acknowledgement, wizard reopened with pre-filled industry, demo data generation race condition on page load, demo/real data coexistence on dismiss, non-owner 403 gate, welcome message per-org flag not per-language, multi-tab server-as-source-of-truth, demo generation failure retry path, org deleted during Celery task. Full catalog in git history.
| # | Claim | Status | Evidence |
|---|---|---|---|
| 1 | GET + PATCH /api/organizations/current/onboarding/ (status + industry save) | ✅ match | E2E tests E4, §7 tests #20–22; T3 BL entry |
| 2 | OnboardingPage.tsx (3-step wizard: industry Select, invite textarea, loading spinner) at frontend/src/pages/settings/OnboardingPage.tsx | ✅ match | T3 BL (2026-06-13); E2E test E3; src/test/OnboardingPage.test.tsx 13 tests (grep-confirmed 2026-07-02: 13 it() calls) |
| 3 | Empty state CTAs (Today “View sample brief”, Projects inline form, Tasks inline form) | ✅ match | T3-D BL (2026-06-13); E2E tests E1, E2 |
| 4 | onboarding i18n namespace (en + vi: step labels, industry names, banner keys) | ✅ match | T3 BL (2026-06-13): keys added to en/vi onboarding.json |
| 5 | Demo banner on Today page (dismissible, “Remove sample data” button) | ✅ match | T3-D BL (2026-06-13); E2E test E1 |
| 6 | Organization.onboarding_completed, demo_data_generated, welcome_message_sent model fields | ✅ match | backend/apps/organizations/models.py L35-37 confirmed 2026-07-02: all three fields present as BooleanField(default=False). The prior “not grep-confirmed” caveat is resolved. |
| 7 | is_demo = BooleanField(default=False) on Task, Commitment, Risk, DecisionRequest | ✅ match | Grep-confirmed 2026-07-02: backend/apps/work/models.py:196 (Task), backend/apps/operations/models.py L59 (Commitment), L112 (Risk), L156 (DecisionRequest). The original “0 hits” audit was incorrect. |
| 8 | backend/apps/onboarding/ app (demo templates dict + generate_demo_data function) | ✅ match | Directory confirmed 2026-07-02: backend/apps/onboarding/ contains apps.py, demo_data.py (DEMO_TEMPLATES dict, 9 industries), tasks.py (generate_demo_data() — plain function, not yet a Celery task; PR-2 note in file says Celery wrap is deferred), urls.py, views.py. App is populated and registered. |
| 9 | POST /api/organizations/current/onboarding/complete/ and DELETE /demo-data/ endpoints | ✅ match | Confirmed 2026-07-02: backend/apps/onboarding/urls.py registers both — onboarding/complete/ → views.complete_onboarding (L69, POST) and demo-data/ → views.demo_data (L100, GET/POST/DELETE dispatch). Both endpoints exist and are wired. |
| 10 | Welcome bot message on first /hub/chat visit (welcome_message_sent guard) | ⚠️ gap | Not mentioned in any BL entry |
| 11 | PostHog activation events (onboarding_step_completed, activation_completed, demo_data_dismissed) | ⚠️ gap | Not mentioned in any BL entry |
| 12 | [Strategy v2 realignment — TO BUILD, not shipped] Onboarding’s first action is the F70 Chaos Import (paste a messy Zalo thread / Excel export / screenshot → Boss Brief from the owner’s OWN data, <10 min, zero setup), not the generic industry-dropdown → demo-brief wizard. The activation event should be “owner gets a useful brief from THEIR OWN pasted chaos” (a brief item recognizably lifted from their input), not “first brief seen” from industry-templated demo data. Concrete flow change: (a) replace/precede §2 Step 1–3 wizard with the F70 paste surface as the primary post-registration screen; (b) demo data (§2 Demo Data Scaffolding) demotes to a fallback for owners with nothing to paste; (c) redefine the §2 Activation Tracking event from demo-data first-brief to own-data first-brief. | 🔲 gap — TO BUILD | Strategy v2 §9.1/§7.2 + registry doctrine rules 4–5 (Positioning doctrine, strategy v2 2026-06-14); F70 is the wedge. Not built — current code ships the generic wizard (rows 1–5). §4 data model / §5 API / §7 tests unchanged by this note. |
12. Open Questions / Decisions
- Demo data quality: Should demo data be hardcoded or LLM-generated per industry? Decision: hardcoded templates. LLM generation at signup time is slow, expensive per user, and stochastic. Curated templates are predictable and load instantly.
- Industry impact on pulse questions: Should pulse questions differ per industry? Decision: not in v1. The 6 standard questions work for all industries. Industry-specific questions are a v2 feature after measuring whether standard questions have adequate response rates.
- Onboarding wizard skippable: Should the wizard be mandatory or skippable? Decision: skippable. Forcing a wizard increases signup abandonment. The demo data still generates in the background — the CEO just doesn’t pick an industry.
- Industry field migration:
Organization.industryexists as free-text (CharField). Should this spec add choices validation at the model level, or keep it loose? Decision: enforce choices at the serializer level (DRFChoiceField), not at the model. Backward compatible with existing orgs that have free-text industry values.
13. Bug / Change Log
2026-07-16 — Contract Graph demo-data POST ownership checkpoint
The exact shipped route POST /api/organizations/current/demo-data/ is now declared under F52
through demo_data, reusing the existing F52.AC002.HAPPY, F52.AC002.IDEMPOTENT, and
F52.AC002.ERROR runner-native proofs. This closes inventory ownership only; backend execution,
browser, native, live-provider, Redis, Sentry, production, F70, and async-generation gaps remain.
2026-07-16 — Contract Graph onboarding-completion ownership checkpoint
The exact route POST /api/organizations/current/onboarding/complete/ is now declared under F52
through complete_onboarding, retaining the existing F52.AC001.COMPLETE, COMPLETE_ERROR, and
ROLE_DENY proofs. This closes inventory ownership only; guarded backend execution remains
F52.GAP008 and no browser, native, provider, Redis, Sentry, or production evidence is inferred.
2026-07-02 — Docs: upgrade falsely-flagged ⚠️ rows in §11 to ✅; correct test count; flip confirmed AC
Type: Documentation Fix
Summary:
- §11 Row 2: test count corrected 7 → 13.
frontend/src/test/OnboardingPage.test.tsxhas 13it()calls (grep-confirmed 2026-07-02). Path confirmed asfrontend/src/pages/settings/OnboardingPage.tsx. - §11 Row 6 (⚠️ partial → ✅):
Organization.onboarding_completed,demo_data_generated,welcome_message_sentare present atbackend/apps/organizations/models.pyL35-37. The prior “not grep-confirmed” caveat is resolved. - §11 Row 7 (⚠️ gap → ✅):
is_demo = BooleanField(default=False)confirmed on all four models —backend/apps/work/models.py:196(Task) andbackend/apps/operations/models.pyL59 (Commitment), L112 (Risk), L156 (DecisionRequest). The original audit’s “0 grep hits” was wrong. - §11 Row 8 (⚠️ gap → ✅):
backend/apps/onboarding/exists and is populated:demo_data.py(DEMO_TEMPLATESdict, 9 industries),tasks.py(generate_demo_data()— plain function; Celery wrap is deferred per PR-2 note in file),urls.py,views.py,apps.py. - §11 Row 9 (⚠️ gap → ✅): both endpoints registered in
backend/apps/onboarding/urls.py—complete_onboarding(views.py:69,POST .../onboarding/complete/) anddemo_data(views.py:100,DELETE .../demo-data/). - §2 AC “Demo data includes: 1 sample project…” →
[x]: manufacturing template indemo_data.pyhas 1 project + 3 tasks (due_days_from_now-1/0/3 = overdue/today/future) + 2 commitments + 1 risk + 1 decision.generate_demo_data()creates all items withis_demo=True. The AC is met. - Remaining
[ ]ACs (11 of 12) are unchanged: demo data as Celery task (Row 8 notes it is still a plain function), immediate brief generation, demo brief content, CEO interaction with demo items, all four PostHog/activation ACs, welcome bot message appearance, Empty Brief CTA, and the F70 strategy v2 realignment (Row 12 “TO BUILD”). - No code changes.
2026-07-02 — Docs: correct stale file paths (batch 2)
Type: Documentation Fix
Summary:
frontend/src/pages/OnboardingPage.tsx(§13 entry 2026-06-14 + PR-3 plan) — page moved tofrontend/src/pages/settings/OnboardingPage.tsxduring the settings IA restructure. Both occurrences corrected.frontend/src/test/OnboardingWizard.test.tsx(PR-3 plan) — file was never created; wizard tests were consolidated into the existingfrontend/src/test/OnboardingPage.test.tsx(7 tests). Annotated as not created.frontend/src/test/EmptyStateCTAs.test.tsx(PR-4 plan) — file was never created; empty-state coverage distributed across page-level test files. Annotated as not created.frontend/src/test/PostHogEvents.test.tsx(PR-5 plan) — file was never created; PostHog activation tracking is an open ⚠️ gap (§11 Row 11). Annotated as not created.- No code changes.
2026-07-02 — Spec drift fix: checked shipped ACs in §2
Type: Documentation
Summary:
- 9 of 21
[ ]ACs in §2 flipped to[x]— confirmed shipped against §11 audit rows 1–5 (all ✅). - 12 ACs remain
[ ]: demo data scaffolding backend (§11 Row 8 ⚠️ gap), immediate brief generation (Row 9 ⚠️ gap), is_demo field (Row 7 ⚠️ gap), activation tracking/PostHog (Row 11 ⚠️ gap), welcome bot message (Row 10 ⚠️ gap), Empty Brief CTA (not confirmed in Row 3 evidence), and the strategy v2 F70 realignment (Row 12, explicitly “TO BUILD”). - No code changes.
2026-06-15 — Frontend route namespaced
Frontend route namespaced: /chat -> /hub/chat and the post-onboarding redirect target /today -> /work/today (app-wide IA restructure; API paths unchanged).
2026-06-14 — Fix: “Skip, take me to Lumi” appeared stuck
Type: Bug Fix
Summary:
handleSkipinfrontend/src/pages/settings/OnboardingPage.tsxawaited the heavyPOST /organizations/current/demo-data/(and then the onboarding PATCH) before callingnavigate('/today?welcome=1'). While demo-data generation ran, the page stayed on the onboarding screen, so Skip looked broken.- Changed Skip to navigate immediately and run the
skipped: truePATCH and demo-data generation as background fire-and-forget calls (errors still swallowed, as before). Both requests are still sent; they no longer block the redirect.
Reason:
- “Skip, take me to Lumi” must be instant — blocking on demo generation contradicts the activation intent (time-to-first-value, no waiting).
Impact:
- Frontend behavior only. No §4 data model, §5 API, or Status change.
handleSkipis now synchronous (fire-and-forget).
Tests:
- Existing
src/test/OnboardingPage.test.tsx“skip link triggers demo generation and navigates to today” still passes (9/9); asserts bothgenerateDemoand navigation to/today?welcome=1.tsc --noEmitclean.
2026-06-14 — Strategy v2 realignment (positioning)
Type: Documentation Change
Summary:
- §1 reframed: the real onboarding is the F70 wedge (paste your own messy Zalo/Excel/screenshot → evidence-cited Boss Brief from the owner’s OWN company, <10 min, zero setup), not the generic industry-dropdown wizard. Honest activation event = “owner gets a useful brief from THEIR OWN pasted chaos”, not “first brief seen” from demo data. The existing wizard is noted as as-built v1, no longer the target.
- §11 added gap row 12 documenting the concrete flow change (F70 paste as primary first action; demo data demoted to fallback; activation event redefined to own-data) as TO BUILD — not shipped.
Reason:
- Align with competitive-strategy-v2.md §9.1/§7.2 (accept the mess, owner’s own data converts) and the registry Positioning doctrine (strategy v2, 2026-06-14) rules 4 (time-to-first-value <10 min, no blank workspace) and 5 (wedge = F70).
Impact:
- Positioning prose only in §1. The behavior/UI/activation change is recorded as a §11 audit gap to fix — not claimed as built. No §4 data model, §5 API, §7 test plan, §11 prior audit-verdict rows, or Status change.
Tests:
- None (spec-only).
Notes:
- §2 wizard, demo-data scaffolding, and the §14 implementation-plan appendix are left intact as the documented as-built v1; the realignment supersedes them as the target via the §11 gap, not by rewriting them.
2026-06-14 — Spec restructured to TEMPLATE.md canonical order (§1–§13) + prior BL entries merged
Type: Documentation Change
Summary:
- §9 Edge Cases (11 items, 9.1–9.11) absorbed into §11 Implementation Audit note.
- §10 Error States → §9, §11 Diagrams & Docs Touched → §10, §12 Implementation Audit (grep-verified Exists/Missing) → §11 4-column table (11 rows, ✅/⚠️ per BL evidence).
- §14 Open Questions / Decisions → §12, §15 Bug / Change Log → §13.
- §13 Implementation Plan (non-standard, 5 PRs) → §14 Implementation Plan Appendix.
Reason:
- Align with TEMPLATE.md canonical section order per SP fleet restructuring pass.
Impact:
- Spec authors and future implementation agents.
2026-06-13 — Spec refinement: numbered edge cases, detailed error matrix, expanded test plan
Type: Documentation Change
Summary:
- Replaced §9 loose prose with 11 numbered edge cases (9.1–9.11) covering invited members, activation reminders, plan pre-selection, race conditions, demo/real data coexistence, role gates, language changes, multi-tab, generation failure, and org deletion scenarios.
- Replaced §10 with 9-row detailed error matrix (user-visible/internal/log/retry).
- Expanded §7 test plan from 9 to 19 test cases, adding coverage for idempotency, locale, member onboarding skip, role permissions, and cross-tenant demo data isolation.
- Updated spec status from draft to agreed.
Reason:
- Spec needed hardening per implementation audit. Edge cases were loose prose, test plan was thin, error states lacked structured detail.
Impact:
- Spec authors and future implementation agents.
Tests:
- Spec-only change — no code tests affected.
2026-06-13 — Implementation audit + implementation plan hardened
Type: Documentation Change
Summary:
- Replaced §12 high-level audit (“Not built. Requires 1,2,3,4”) with grep-verified detailed Implementation Audit listing every missing file/path and three existing design constraints.
- Added §13 implementation plan with 5 ordered PRs: PR-1 (backend models + onboarding app + demo templates), PR-2 (API endpoints), PR-3 (frontend wizard), PR-4 (demo banner + empty states + welcome message), PR-5 (activation tracking + pilot banner). Each PR has tests-first list, backend models/endpoints, frontend routes/components, i18n namespace, and verification gate.
- Added edge case §9.11 (org deleted during demo generation), §9.4 (industry selection previously made), §9.10 (demo data generation fails).
- Added Open Question §14 item 4 (industry field migration strategy — serializer-level enforcement, not model-level).
- Verified spec aligns with TEMPLATE structure and MVP_SCOPE.md registry entry.
Reason:
- Per fleet task D-r04: specs must be build-ready with concrete implementation audits and PR-scoped plans.
Impact:
- Spec authors and future code rounds executing F52.
Tests:
- Spec-only change — no code tests affected.
2026-06-13 — T3 fleet: onboarding wizard steps 1–3 implemented
Type: Feature
Summary:
- Replaced
<select>element with shadcn<Select>component (base-ui). - Added step progress dots indicator (steps 1–2 only, step 3 is full-screen).
- Replaced Card-based step 3 with full-screen auto-loading spinner + “Lumi is preparing your morning brief…” text.
- Step 2: replaced
<Input>with<textarea>, added email count display. - Step 2 “Finish setup” now auto-triggers demo generation + API update, transitions to step 3 spinner, then redirects to /today after ~5s via setTimeout.
- Added i18n keys:
next,finish,select_industry,emails_placeholder,invitations,brief_loading. - Updated test file (7 tests, removed stale step-3 button tests, added loading-spinner and email-count tests).
Reason:
- Per task B-r10: implement steps 1–3 with shadcn Select, step dots, textarea + invitation count, and step 3 auto-loading animation.
Impact:
- OnboardingPage.tsx, en/vi onboarding.json, OnboardingPage.test.tsx, MVP_SCOPE.md.
Tests:
src/test/OnboardingPage.test.tsx— 7 tests passing, updated assertions for new UI.- Frontend gate:
tsc --noEmit(0 errors),pnpm vitest run src/test/OnboardingPage.test.tsx(7/7 pass).
2026-06-13 — T3-D R10: Empty States implemented
Type: Feature
Summary:
TodayPage.tsx:isEmptyguard — when tasks/commitments/risks/decisions all empty, shows “Nothing needs your attention today.” + “View sample brief” CTA →/brief.ProjectsPage.tsx: dashed<Card>with “Create your first project” +Plusbutton opening inline form.TasksPage.tsx: dashed<Card>with “Create your first task” +Plusbutton opening inline form.- i18n keys added to
en/today.json+vi/today.json:empty_state_text,view_sample_brief. - i18n keys added to
en/projects.json+vi/projects.json:empty_state_text. - i18n keys added to
en/tasks.json+vi/tasks.json:empty_state_text.
Files: TodayPage.tsx, ProjectsPage.tsx, TasksPage.tsx, locale files for today/projects/tasks.
14. Implementation Plan (Appendix)
PR-1: Backend foundation — Organization fields + onboarding app
Tests-first (create backend/tests/test_onboarding.py):
TestOrganizationFields::test_onboarding_completed_default— field is False for new orgsTestOrganizationFields::test_demo_data_generated_default— field is False for new orgsTestOrganizationFields::test_welcome_message_sent_default— field is False for new orgsTestDemoDataTemplates::test_all_industries_have_templates— DEMO_TEMPLATES dict has entries for all 9 industriesTestDemoDataTemplates::test_industry_template_structure— each template has project/tasks/commitments/risks/decisionsTestDemoDataGeneration::test_generates_demo_data— Celery task creates Task/Commitment/Risk/DecisionRequest withis_demo=TrueTestDemoDataGeneration::test_idempotent— second call returns existing, no duplicateTestDemoDataGeneration::test_industry_other_default— no industry specified → uses “other” templateTestDemoDataGeneration::test_org_deleted_during_generation— task exits silently if org gone (check.exists())
Backend:
- Migration: add
onboarding_completed,demo_data_generated,welcome_message_senttoOrganization - Migration: add
is_demo = BooleanField(default=False)toTask,Commitment,Risk,DecisionRequest - New app
backend/apps/onboarding/:demo_data.py—DEMO_TEMPLATESdict (9 industries, ~30 lines per template)tasks.py—generate_demo_dataCelery task. Creates 1 sample project, 3–5 tasks, 2–3 commitments, 1–2 risks, 1 decision. All withis_demo=True. SetsOrganization.demo_data_generated = Trueon success.- Wire into
apps/onboarding/apps.pyready()
- Register in
config/settings/base.pyINSTALLED_APPS
Gate: cd backend && DJANGO_SETTINGS_MODULE=config.settings.test python -m pytest tests/test_onboarding.py -x --cov=apps --cov=core --cov-fail-under=99 --tb=short
PR-2: Backend API endpoints
Tests-first (add to backend/tests/test_onboarding.py):
TestOnboardingComplete::test_owner_can_complete— POST setsonboarding_completed=TrueTestOnboardingComplete::test_non_owner_gets_403— admin/manager/member/viewer get 403TestOnboardingComplete::test_invalid_industry_400— industry not in choices → 400TestDemoDataGenerate::test_owner_can_generate— POST triggers Celery taskTestDemoDataGenerate::test_non_owner_403— 403 for non-ownerTestDemoDataDelete::test_deletes_only_is_demo_items— real items preservedTestDemoDataDelete::test_partial_delete_can_retry— if some items fail, remaining is_demo=True items are cleanedTestActivationStatus::test_returns_status— onboarding_completed + demo_data_generated + team_count + brief_countTestDemoDataIsolation::test_cross_tenant_403— org B cannot delete org A’s demo dataTestWelcomeMessage::test_only_once— POST sets welcome_message_sent, second call is no-opTestWelcomeMessage::test_non_owner_403
Backend:
onboarding/views.py—OnboardingCompleteView,DemoDataGenerateView,DemoDataDeleteView,ActivationStatusView,WelcomeMessageViewonboarding/urls.py— routes:POST /api/organizations/<id>/onboarding/complete/POST /api/organizations/<id>/demo-data/generate/DELETE /api/organizations/<id>/demo-data/GET /api/organizations/<id>/activation-status/POST /api/organizations/<id>/welcome-message/
- Register URLs in project root
urls.py - Wire auto-generation: after org creation (in
OrganizationViewSet.create), callgenerate_demo_data.delay(org_id, industry)
Gate: same backend gate as PR-1 + targeted E2E for owner-only endpoints
PR-3: Frontend — Onboarding Wizard (3-step)
Tests-first:
frontend/src/test/OnboardingWizard.test.tsx(not created — wizard tests were consolidated intofrontend/src/test/OnboardingPage.test.tsx):renders step 1 industry selectionnavigates to step 2 on Continuestep 2 shows input field + skipstep 3 shows loading animationskip button skips wizard, triggers demo generationback button preserves industry selectionapi error shows toast with retry
frontend/tests/e2e/lumi.spec.ts—describe("Onboarding"):completes 3-step wizard flowskip button generates demo data silentlywizard not shown for invited members
Frontend:
frontend/src/pages/settings/OnboardingPage.tsx— 3-step wizard: industry dropdown → invite team → “preparing brief” loading animation. Skip link at bottom.- Route
/onboardinginApp.tsx— displayed after org creation, before/today - Guard: skip if
onboarding_completed=True(redirect to/today) frontend/src/i18n/locales/en/onboarding.json+vi/onboarding.json
Gate: cd frontend && tsc --noEmit && pnpm test
PR-4: Frontend — Demo banner, empty state CTAs, welcome message
Tests-first:
frontend/src/test/TodayPage.test.tsx— demo banner renders with dismiss, “Remove sample data” deletesfrontend/src/test/ChatPage.test.tsx— welcome message appears on first visit, not on subsequentfrontend/src/test/EmptyStateCTAs.test.tsx(not created — empty-state CTA coverage is distributed across page-level test files:TodayPage.test.tsx,BriefPage.test.tsx) — each page (Today/Brief/Projects/Tasks) shows correct CTA content- E2E: existing
describe("Onboarding")extended — demo badge visible, banner dismiss works, welcome message appears in chat
Frontend:
TodayPage.tsx— demo banner (dismissible), “Remove sample data” button, pilot countdown bannerBriefPage.tsx,TodayPage.tsx,ProjectsPage.tsx,TasksPage.tsx— F52-specified empty state CTAsChatPage.tsx— checkwelcome_message_sent, callPOST /welcome-message/to mark, show welcome content- i18n: extend
onboardingnamespace with banner/welcome-message keys
Gate: cd frontend && tsc --noEmit && pnpm test && make e2e
PR-5: Activation tracking + pilot banner wiring
Tests-first:
frontend/src/test/PostHogEvents.test.tsx(not created — PostHog activation-tracking is a ⚠️ gap per §11 Row 11; no test file exists) — events fire at correct momentsbackend/tests/test_onboarding.py— pilot expiry calculated correctly fromSubscription.expires_at- E2E: pilot banner shows correct days, yellow warning at 48h
Frontend:
- PostHog
onboarding_step_completedevent on each wizard step - PostHog
activation_completedon first brief view with ≥1 item - PostHog
demo_data_dismissedon banner dismissal - Pilot banner component in
TodayPage.tsx— readsSubscription.expires_at, shows countdown
Gate: full test suite + E2E
The exact GET /api/organizations/current/onboarding/ read surface is registered to
onboarding_progress with F52.AC001.PROGRESS_API; its repository proof does not establish
deployed tenant, browser, native, live-provider, or production activation evidence.
The exact PATCH /api/organizations/current/onboarding/ mutation is separately registered to
onboarding_progress with F52.AC001.PATCH; its repository proof does not establish backend
runner, browser, native, live-provider, or production activation evidence.