diff --git a/docs/superpowers/specs/2026-03-16-empty-states-onboarding-exports-design.md b/docs/superpowers/specs/2026-03-16-empty-states-onboarding-exports-design.md index 9b71ffd8..b3087993 100644 --- a/docs/superpowers/specs/2026-03-16-empty-states-onboarding-exports-design.md +++ b/docs/superpowers/specs/2026-03-16-empty-states-onboarding-exports-design.md @@ -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