# Empty States, Onboarding & Professional Exports — Design Spec

> **Date:** 2026-03-16
> **Product:** ResolutionFlow
> **Approach:** Bottom-up (foundation → empty states → onboarding → exports)

---

## Purpose

Make ResolutionFlow feel polished and professional by eliminating dead-end empty pages, guiding new users through setup, and providing client-ready PDF exports that MSPs can hand directly to customers.

---

## Scope

### In Scope

1. Illustrative empty states across 8 pages with benefit-oriented copy and "Learn more" guide links
2. Onboarding starter checklist widget on QuickStartPage (solo and team variants)
3. Team branding settings (logo upload, company display name)
4. PDF export via WeasyPrint with branded templates
5. Supporting data capture during sessions (text snippets + screenshots)
6. 7 in-app user guides linked from empty states
7. Tests: backend integration, frontend unit, Playwright e2e

### Out of Scope

- Bring-your-own-storage (S3/Azure) for supporting data — future feature
- Full file attachments beyond screenshots
- Removing "Powered by ResolutionFlow" footer (potential premium tier)
- Multi-browser Playwright matrix

---

## 1. Empty States

### Component Upgrade

Extend the existing `EmptyState.tsx` component to support the illustrative style:

- **SVG illustration slot** — optional prop, renders a brand-colored line-art illustration above the title
- **Benefit-oriented description** — explains what the page does and why it matters, not just "no data"
- **Primary CTA button** — navigates to the action that populates the page
- **Secondary "Learn more" link** — navigates to the relevant in-app guide

### Pages (8 total)

| Page | Title | Description | CTA | Guide Link |
|------|-------|-------------|-----|------------|
| Flow Library (no flows) | Build your first troubleshooting flow | Flows guide your team through proven resolution paths, capturing every decision along the way. | Create a Flow | `/guides/creating-flows` |
| Flow Library (no filter results) | No flows match your filters | Try adjusting your search or filters. | Clear Filters | — |
| Analytics (My/Team) | Track your troubleshooting performance | Analytics show resolution times, common paths, and team efficiency. Data appears automatically as you complete sessions. | Run Your First Session | `/guides/understanding-analytics` |
| Session History (empty) | Your session history will appear here | Every troubleshooting session is recorded with decisions, timing, and outcomes — ready for export or review. | Start a Session | `/guides/running-sessions` |
| Integrations | Connect your PSA for seamless workflows | Link ConnectWise or other PSA tools to pull ticket context into sessions and push documentation back automatically. | Connect Integration | `/guides/psa-setup` |
| Step Library (empty) | Build a reusable step library | Save common troubleshooting steps once, reuse them across flows. Keeps your team consistent and saves build time. | Browse Steps | `/guides/step-library` |
| Script Library (empty) | Automate with script templates | Pre-built and custom scripts your team can reference during sessions. PowerShell, bash, and more. | Explore Templates | `/guides/script-templates` |
| My Shares (empty) | Share session results with your team | Create shareable links to completed sessions for knowledge sharing and client communication. | View Sessions | `/guides/sharing-sessions` |

### Illustrations

Simple SVG line art using the cyan brand color palette (`#06b6d4` → `#22d3ee`). Each page gets a unique illustration relevant to its content. Lightweight — no complex animations or heavy graphics.

### Visual Style

- Container: centered content within the page's existing layout
- Illustration: 60-80px height, `opacity: 0.4` → `0.7` range (needs to be visible on `#101114` dark background)
- Title: `text-foreground`, `text-lg` (18px), `font-semibold` (matches existing `EmptyState.tsx`)
- Description: `text-muted-foreground`, 13px, max-width ~400px for readability
- CTA: `bg-gradient-brand` primary button style
- Learn more: `text-muted-foreground` with `→` arrow, hover brightens

---

## 2. Onboarding Starter Checklist

### Location

Dismissible `.glass-card` widget on QuickStartPage, positioned below the greeting and above the stats/activity sections.

### Visibility Rules

- Shows for users who haven't dismissed it and haven't completed all items
- Auto-hides with a brief "You're all set!" state once all items are checked, then disappears
- Dismissible at any time via "×" button
- Dismissed/completed state stored in a new `onboarding_dismissed` Boolean column on the `users` table (requires migration). Not using JSON — a simple column is clearer and queryable.
- Never reappears once dismissed or completed

### Completion Tracking

No new database table. A single API endpoint queries existing data to determine completion status.

**Endpoint:** `GET /api/v1/users/onboarding-status`

**Response:**

```json
{
  "created_flow": true,
  "ran_session": false,
  "exported_session": false,
  "tried_ai_assistant": false,
  "invited_teammate": false,
  "connected_psa": false,
  "is_team_user": true,
  "dismissed": false
}
```

**Completion queries:**

| Item | Condition |
|------|-----------|
| `created_flow` | User owns at least 1 tree |
| `ran_session` | User has at least 1 session |
| `exported_session` | User has at least 1 session with `exported=True` |
| `tried_ai_assistant` | User has at least 1 assistant chat |
| `invited_teammate` | Team has more than 1 member |
| `connected_psa` | Team has at least 1 PSA connection |

**Dismiss endpoint:** `POST /api/v1/users/onboarding-status/dismiss` — sets `onboarding_dismissed=True` on the user record.

### Checklist Variants

**Solo pro (4 items):**

1. Create your first flow → navigates to Flow Library
2. Run your first session → navigates to Flow Library
3. Export a session → navigates to Session History
4. Try the AI assistant → navigates to AI Chat

**Team admin (5 items):**

1. Create your first flow → navigates to Flow Library
2. Invite a team member → navigates to Team Settings
3. Run your first session → navigates to Flow Library
4. Connect a PSA integration → navigates to Integrations
5. Export a session → navigates to Session History

### Visual Design

- `.glass-card` container with `border-radius: 16px`
- Cyan progress bar at top showing completion (e.g., "2 of 5 complete")
- Section label: "Getting Started" in `font-label text-[0.625rem] uppercase tracking-[0.1em]`
- Each item: checkbox (auto-checked with cyan fill when complete) + label + subtle navigation arrow
- Completed items: muted text with cyan checkmark
- Uncompleted items: `text-foreground` with hover highlight, clickable to navigate

---

## 3. Team Branding & Logo Upload

### Location

New "Branding" section on the existing Team Settings page. Team admin only. Solo pros get a simpler version on their Account Settings page.

### Fields

- **Company logo** — image upload (PNG, JPG, or SVG, max 2MB)
- **Company display name** — text field, falls back to team name if empty
- **Logo preview** — shows how the logo will appear on exports

### Backend

**New columns on `teams` table:**

- `logo_data` — Text, base64-encoded image data, nullable
- `logo_content_type` — String (e.g., `image/png`), nullable
- `company_display_name` — String, nullable (falls back to `team.name`)

**Endpoints:**

- `PATCH /api/v1/teams/{team_id}/branding` — upload logo (multipart form) + display name. Team admin only.
- `GET /api/v1/teams/{team_id}/branding` — retrieve logo data + display name. Any team member.
- `DELETE /api/v1/teams/{team_id}/branding/logo` — remove logo. Team admin only.

**Validation:**

- File size: max 2MB
- Content type: `image/png`, `image/jpeg`, `image/svg+xml`
- Solo pros: branding columns (`logo_data`, `logo_content_type`, `company_display_name`) added directly to the `users` table. Same schema as teams. Solo pros without a team still get branded exports.

### Why Base64 in DB

Logos are small (< 2MB raw, ~2.67MB base64-encoded) and there's one per team/user. The 2MB validation limit applies to the raw uploaded file size (before base64 encoding). Avoids S3/file storage dependency entirely. Easy to migrate to object storage later when BYOS is implemented for supporting data.

---

## 4. PDF Export via WeasyPrint

### Backend

**New dependency:** `weasyprint` in `requirements.txt`

**System dependencies (required by WeasyPrint):**
- `libpango1.0-dev`, `libcairo2-dev`, `libgdk-pixbuf2.0-dev`, `libffi-dev`
- Must be added to the Railway Dockerfile via `apt-get install`
- Local dev: install via system package manager (`apt-get` on Ubuntu/Debian)
- CI: add to the e2e job's setup step

**Export service changes:**

- New `generate_pdf()` method in `export_service.py`
- Renders a Jinja2 HTML template with session data + branding, then converts to PDF via WeasyPrint
- Template location: `backend/app/templates/export_pdf.html`

**Existing endpoint change:**

- `POST /sessions/{session_id}/export` gains `format: "pdf"` option
- Update `SessionExport` schema: change `format` field pattern from `^(text|markdown|html|psa)$` to `^(text|markdown|html|psa|pdf)$`
- PDF format returns `Response(content=pdf_bytes, media_type="application/pdf")` with `Content-Disposition: attachment; filename="session-export-{id}.pdf"` header (different return type from the existing `PlainTextResponse` used by other formats — endpoint must branch on format)
- Non-PDF formats continue returning `PlainTextResponse` as before

### PDF Template Structure

Matches the approved mockup layout:

1. **Header** — Report type label (e.g., "Troubleshooting Report"), flow title, MSP logo or ResolutionFlow logo, company name
2. **Metadata grid** — Engineer, Client, Ticket #, Date, Duration, Outcome (3×2 grid)
3. **Summary** — AI-generated session summary (from existing feature)
4. **Troubleshooting Path** — Visual timeline with cyan step dots, step titles, and decisions at each node. Final resolution step uses green dot.
5. **Supporting Data** — Labeled text snippets (rendered as code blocks) + embedded screenshot images
6. **Footer** — Generation timestamp (left) + "Powered by ResolutionFlow" (right)

### CSS/Styling

- White background, dark text (print-optimized)
- Cyan accent color (`#06b6d4`) for section borders, timeline dots, and branding
- `@page` rules for margins, header/footer positioning
- Page break before Supporting Data section if content runs long
- `break-inside: avoid` on individual supporting data items
- JetBrains Mono for code/command output blocks

### Frontend Changes

- Add "PDF" to the format selector in `ExportPreviewModal`
- PDF option triggers a direct file download (no textarea preview — PDFs aren't editable inline). The modal should switch to a "download-only" mode when PDF is selected: hide the textarea, show a download button with loading state. The format selector stays visible for switching between formats.
- Show a loading spinner while PDF generates server-side
- Existing formats (markdown, text, HTML, PSA) continue to work as before with the textarea preview

### Branding Logic

1. If team has a logo → use team logo + company display name in header, "Powered by ResolutionFlow" in footer
2. If no team logo → use ResolutionFlow logo in header, no "Powered by" footer (it's already the primary brand)
3. Solo pro with logo → same as team logo behavior

---

## 5. Supporting Data Capture

### Database

**New table: `session_supporting_data`**

| Column | Type | Notes |
|--------|------|-------|
| `id` | UUID | Primary key |
| `session_id` | UUID | FK to sessions |
| `label` | String(255) | User-provided label (e.g., "Port Scan Output") |
| `data_type` | Enum | `text_snippet` or `screenshot` |
| `content` | Text | Raw text or base64-encoded image |
| `content_type` | String(50) | Nullable. e.g., `image/png` for screenshots |
| `sort_order` | Integer | Display ordering |
| `created_at` | DateTime(timezone=True) | Auto-set |
| `updated_at` | DateTime(timezone=True) | Auto-set, auto-update |

### API Endpoints

- `POST /api/v1/sessions/{session_id}/supporting-data` — add an item (label, type, content). Returns created item.
- `GET /api/v1/sessions/{session_id}/supporting-data` — list all items for a session, ordered by `sort_order`.
- `PATCH /api/v1/sessions/{session_id}/supporting-data/{id}` — update label or content.
- `DELETE /api/v1/sessions/{session_id}/supporting-data/{id}` — remove an item.

### Validation

- Image size: max 2MB per screenshot (keeps DB growth manageable — at 20 items × 2.67MB base64 = ~53MB worst case per session)
- Text snippet: max 50,000 characters
- Max 20 items per session
- Only the session owner or team admins can add/delete
- Monitor DB size growth in production — if supporting data exceeds expectations, prioritize BYOS migration

### Session Runner UI

- **"Add Supporting Data" button** — positioned near the existing notes input in both troubleshooting and procedural session runners
- **Add modal** with two tabs/options:
  - **Text Snippet** — label input + multiline textarea
  - **Screenshot** — label input + drag-and-drop zone / file picker + clipboard paste (`Ctrl+V`) support
- **Supporting data list** — collapsible section below session notes showing added items:
  - Each item: type icon (code bracket for text, image icon for screenshot) + label + preview (truncated text or thumbnail) + delete button
  - No reordering in v1 — items display in creation order

### Export Integration

Supporting data is included in all export formats:

| Format | Text Snippets | Screenshots |
|--------|--------------|-------------|
| Markdown | Labeled fenced code blocks | `![label](data:image/...)` or `[Screenshot: label]` |
| Plain Text | Labeled indented blocks | `[Screenshot: {label}]` placeholder |
| HTML | `<pre>` blocks with labels | `<img>` tags with base64 src |
| PSA | Labeled code blocks (markdown) | `[Screenshot: {label}]` placeholder |
| PDF | Styled code blocks matching mockup | Embedded images |

---

## 6. User Guides

### Route

`/guides/:slug` — new frontend route inside the authenticated app shell.

### Implementation

- Markdown files stored in `frontend/src/content/guides/`
- Written as React components in `frontend/src/pages/guides/` (avoids `react-markdown` dependency for only 7 short pages). Each guide is a simple functional component using existing typography classes.
- Displayed in a `.glass-card-static` container within the standard app shell layout
- Simple breadcrumb: "Guides → {title}"
- Images/illustrations stored in `frontend/public/guides/` and referenced via absolute paths
- Unknown slugs show a "Guide not found" empty state with a link back to the dashboard

### Guides (7)

| Slug | Title | Content Covers |
|------|-------|---------------|
| `creating-flows` | Creating Flows | Manual flow creation, AI-assisted creation, flow types (troubleshooting, procedural, maintenance), basic editor usage |
| `understanding-analytics` | Understanding Analytics | What each metric means, how data populates over time, team vs personal views |
| `running-sessions` | Running Sessions | Starting a session, navigating decisions, adding notes, adding supporting data, completing and exporting |
| `psa-setup` | Connecting Your PSA | ConnectWise setup walkthrough, where to find API credentials, what the integration enables |
| `step-library` | Using the Step Library | Browsing shared steps, adding steps to flows, creating reusable steps |
| `script-templates` | Script Templates | Browsing templates, using scripts during sessions, creating custom templates |
| `sharing-sessions` | Sharing Sessions | Creating share links, public vs account-only access, revoking shares |

### Guide Content Style

- Concise — each guide should be 300-600 words
- Task-oriented — "How to do X" structure, not reference documentation
- Include relevant screenshots/illustrations where helpful
- End with a CTA that links back to the relevant feature page

---

## 7. Testing

### Backend Integration Tests (pytest)

**Onboarding status:**
- Returns correct booleans for a fresh user (all false)
- Returns `created_flow: true` after user creates a tree
- Returns `ran_session: true` after user starts a session
- Returns correct `is_team_user` flag
- Dismiss endpoint sets `dismissed: true`

**Team branding:**
- Upload logo — stores base64, returns success
- Upload oversized file — returns 400
- Upload invalid content type — returns 400
- Retrieve branding — returns logo data + display name
- Delete logo — clears logo data
- Non-admin cannot update branding — returns 403

**Supporting data:**
- Create text snippet — stores and returns item
- Create screenshot — stores base64 and returns item
- List items — returns in sort order
- Delete item — removes from DB
- Exceed 20 item limit — returns 400
- Exceed 2MB screenshot — returns 400
- Non-owner cannot add to session — returns 403

**PDF export:**
- Generate PDF — returns valid PDF bytes with correct content type
- PDF includes branding when team has logo
- PDF uses ResolutionFlow defaults when no team logo
- PDF includes supporting data items
- PDF handles session with no supporting data gracefully

### Frontend Unit Tests (Vitest)

- `EmptyState` component — renders illustration, title, description, CTA, and learn more link with correct props
- `EmptyState` without optional props — renders without illustration or learn more link
- Onboarding checklist — renders correct items for solo vs team user
- Onboarding checklist — completed items show cyan checkmark and muted style
- Onboarding checklist — dismiss button calls dismiss endpoint
- Export format selector — includes PDF option
- Export modal — PDF selection triggers download behavior instead of textarea preview

### Playwright E2E Tests

- **Empty state flow** — log in as fresh user, navigate to Flow Library, verify empty state renders with CTA and "Learn more" link
- **Onboarding checklist** — log in as fresh user, verify checklist visible on dashboard, create a flow, verify checklist item updates
- **PDF export** — complete a session, navigate to session detail, select PDF format, verify download triggers
- **Guide page** — click "Learn more" from an empty state, verify guide page loads with content

These Playwright tests focus on happy paths only — one representative flow per feature area.

---

## Implementation Order (Bottom-Up)

### PR 1: Backend Foundation
- `onboarding_dismissed` column on `users` table + migration
- Team branding columns (`logo_data`, `logo_content_type`, `company_display_name`) on `teams` table + migration
- Solo pro branding columns on `users` table + migration (can combine with onboarding migration)
- Branding CRUD endpoints
- Supporting data table + migration
- Supporting data CRUD endpoints (POST, GET, PATCH, DELETE)
- Onboarding status endpoint + dismiss endpoint
- WeasyPrint dependency + system deps in Dockerfile + PDF generation in export service
- Update `SessionExport` schema format pattern to include `pdf`
- Backend tests for all of the above

### PR 2: Empty States + Guides
- Upgrade `EmptyState.tsx` component
- Roll out across 8 pages
- Create 7 markdown guides
- Add `/guides/:slug` route
- Frontend unit tests for EmptyState

### PR 3: Onboarding Checklist
- QuickStartPage checklist widget
- Solo vs team variant logic
- Dismiss and auto-complete behavior
- Frontend unit tests
- Playwright test for checklist

### PR 4: PDF Export + Supporting Data UI
- Supporting data capture in session runner
- PDF format option in ExportPreviewModal
- Team branding section on Team Settings page
- Playwright tests for export and empty states

---

## Future Considerations

- **BYOS (Bring Your Own Storage):** Allow MSPs to configure their own S3/Azure blob storage for supporting data. Removes our storage burden and addresses data sovereignty.
- **Premium branding tier:** Option to remove "Powered by ResolutionFlow" footer for higher-tier plans.
- **Full file attachments:** Extend supporting data to accept arbitrary file types (logs, configs, CSVs) once object storage is in place.
- **Export templates:** Let teams customize the PDF template layout, colors, and sections included.
- **Onboarding expansion:** Feature tours, tooltips, and contextual help beyond the starter checklist.
