feat: empty states, onboarding checklist, PDF exports, and supporting data #114

Merged
chihlasm merged 16 commits from feat/backend-foundation-empty-states-exports into main 2026-03-18 00:42:30 +00:00
Showing only changes of commit 4add88f5ec - Show all commits

View File

@@ -64,8 +64,8 @@ Simple SVG line art using the cyan brand color palette (`#06b6d4` → `#22d3ee`)
### Visual Style
- Container: centered content within the page's existing layout
- Illustration: 60-80px height, `opacity: 0.3``0.5` range for subtlety
- Title: `text-foreground`, 14px, `font-semibold`
- 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
@@ -83,7 +83,7 @@ Dismissible `.glass-card` widget on QuickStartPage, positioned below the greetin
- 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 `user_preferences` (existing JSON column)
- 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
@@ -118,7 +118,7 @@ No new database table. A single API endpoint queries existing data to determine
| `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 `dismissed=true` in user preferences.
**Dismiss endpoint:** `POST /api/v1/users/onboarding-status/dismiss` — sets `onboarding_dismissed=True` on the user record.
### Checklist Variants
@@ -178,11 +178,11 @@ New "Branding" section on the existing Team Settings page. Team admin only. Solo
- File size: max 2MB
- Content type: `image/png`, `image/jpeg`, `image/svg+xml`
- Solo pros: same fields stored on user record or preferences — decided during implementation
- 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) and there's one per team. Avoids S3/file storage dependency entirely. Easy to migrate to object storage later when BYOS is implemented for supporting data.
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.
---
@@ -192,6 +192,12 @@ Logos are small (< 2MB) and there's one per team. Avoids S3/file storage depende
**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`
@@ -201,7 +207,9 @@ Logos are small (< 2MB) and there's one per team. Avoids S3/file storage depende
**Existing endpoint change:**
- `POST /sessions/{session_id}/export` gains `format: "pdf"` option
- Returns `application/pdf` with `Content-Disposition: attachment; filename="session-export-{id}.pdf"` header
- 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
@@ -226,9 +234,9 @@ Matches the approved mockup layout:
### 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)
- 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
- Existing formats (markdown, text, HTML, PSA) continue to work as before with the textarea preview
### Branding Logic
@@ -254,19 +262,22 @@ Matches the approved mockup layout:
| `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 5MB per screenshot
- 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
@@ -301,9 +312,11 @@ Supporting data is included in all export formats:
### Implementation
- Markdown files stored in `frontend/src/content/guides/`
- Rendered with `react-markdown` (or similar lightweight renderer)
- 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)
@@ -385,12 +398,15 @@ These Playwright tests focus on happy paths only — one representative flow per
## Implementation Order (Bottom-Up)
### PR 1: Backend Foundation
- Team branding columns + migration
- `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
- Onboarding status endpoint
- WeasyPrint dependency + PDF generation in export service
- 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