Files
greeneclipse 8580c996c3 Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
2026-08-09 22:47:43 +02:00

229 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# UX backlog (P0 / P1)
Date: 2026-08-08 (P0-8 / P0-9 cutover waivers)
Lens: SaaS PIM / feed activation (not legacy-parity — see `docs/design-gaps.md`).
Paths verified under `apps/web`.
**Status legend:** [x] landed · [w] MVP landed + remainder waived for cutover · [~] partial · [ ] open — Wave 8 closeout (2026-08-04); P0-8/9 waivers 2026-08-08.
**Wave 8 closeout (executive):** Staging **GO** / production cutover **NO-GO**. Suite **PASS** ([e239d01f](e239d01f-8ba9-4d3d-92a9-8b6ad7aec7ec); earlier `-race` green). Soft Undo **PASS**; readiness **200**. Bare `/settings` re-smoke **PASS** (tabs; no `replaceState` error; Alerts 6 switches; fix [ddad0c89](ddad0c89-9a46-429f-bb7d-7ae77213b399)). Landed: **P0-10** set-password CTA; **P0-11** role repair; **P0-12** missing-plan/`past_due`; **P0-13** store reconnect; **P0-14** API-key honesty; **P1-13** migrated checklist; **P1-14** invite email mismatch; **P1-15** admin readiness; **P1-16** ETL gaps panel. **P0-8 / P0-9** MVP closed; deferred remainder **waived** for cutover (see signed waivers below). Landed **P1-17** hypercare report-missing CTA (`HYPERCARE_MODE` + support ticket create). Open: operator SMTP/Clerk/Stripe. See `docs/production-readiness.md`.
**Wave 7 closeout (retained):** Staging **GO**; production cutover **NO-GO**. Deep E2E FAIL items re-verified **PASS**: Mapping incomplete on load; Alerts 6 switches failures ON; mapping chips **PASS**. Prior PASS: login, Needs Review, processing, billing wallet, products pager.
**Wave 6 closeout (retained):** P0-7 [x]; P0-8 [~]; P0-9 [~]; Mapped vs Active + Mapping incomplete; smoke polish.
**Wave 8 landed:** P0-10 [x] login `password_not_set` CTA; P0-12 [x] missing-plan / past_due recovery + migrator plan-skip tooling; P0-13 [x] store reconnect cliff; P0-14 [x] API key reissue honesty; P1-13 [x] migrated-cohort checklist; P1-14 [x] invite email mismatch; P1-15 [x] admin readiness strip (`GET /api/admin/readiness`); FTP sync honesty; empty-shell polish (vector-categories / structured-descriptions / campaigns); bulk Accept Undo (admin→reset); Settings `replaceState` race fix; staging-auth-rehearsal scripts; security residual (team PATCH/DELETE last-admin, Stripe mock blocked in prod / live secrets runtime-gated, set-password 429, CSP connect-src); OpenAPI team PATCH + set-password; campaigns list N+1; a11y alertdialog / Alerts labels / ⌘K aria-live. Aug-8: see [production-readiness.md](production-readiness.md) (Stripe boot honesty, `mail.ApplyDryRun`, public export opaque 404 **BREAKING**, adapter pinned to `adapter-node`).
**Wave 7 landed (retained):** P1-3 [x] bulk confirms; P1-10 [x] ⌘K; P0-11 [x] role repair; P0-8 field-level accept/discard; CSP; Stripe/SMTP fail-closed; CSRF cookie sync; maintenance banner; OpenAPI drift; export-feeds empty/progress.
**Smoke polish (landed):** checklist advances from workspace evidence (P0-1); Active Feeds Mapped vs Active + Mapping incomplete (P0-6); mapping confidence chips + fuzzy confirm (P0-7); single sync-gate toast; no Cancel/Retry on completed jobs (P0-3); billing honesty (P0-12); products “Showing XY” (P0-5).
## P0 — ship first
| Status | ID | Item | Targets |
|--------|----|------|---------|
| [x] | **P0-1** | Activation checklist on dashboard (dismissible / resumable; advances from workspace evidence) | `src/routes/dashboard/+page.svelte`, `src/lib/components/DashboardStats.svelte`, `src/lib/tutorial/storage.ts` |
| [x] | **P0-2** | Empty-state primary CTAs (import / connect store / add feed) | `src/lib/components/products/ProductEmptyState.svelte`, `src/routes/products/+page.svelte`, `src/routes/feeds/+page.svelte`, `src/routes/stores/+page.svelte` |
| [x] | **P0-3** | Unify job progress (sync + AI): status, %, “View progress” from toast; Cancel/Retry only when actionable | `src/routes/processing/+page.svelte`, `src/lib/components/ui/Progress.svelte`, `src/lib/components/feeds/FeedActionsMenu.svelte`, `src/routes/feeds/+page.svelte`, `src/lib/components/ui/toast-state.ts`, `src/routes/+layout.svelte` |
| [x] | **P0-4** | Bulk result receipt (succeeded / skipped / failed + link to jobs) | `src/lib/components/products/ProductProcessingActions.svelte`, `src/routes/products/+page.svelte`, `src/routes/processing/+page.svelte` |
| [x] | **P0-5** | Select-all clarity: “N on this page” vs all matching; range “Showing XY” | `src/lib/components/products/ProductTable.svelte`, `src/routes/products/+page.svelte` |
| [x] | **P0-6** | Sync / map preflight (required unmapped fields, sample validation) — UI + API gate; feed Mapped vs Mapping incomplete | `src/routes/feeds/[feedId]/mapping/+page.svelte`, `src/lib/components/feeds/suggest-mappings.ts`, `src/routes/feeds/+page.svelte`, `apps/api/internal/feeds/sync.go` |
## P1 — next
| Status | ID | Item | Targets |
|--------|----|------|---------|
| [x] | **P1-1** | Shorten tour to ≤56 activation-path steps | `src/lib/tutorial/steps.ts`, `src/lib/components/tutorial/TutorialOverlay.svelte` |
| [~] | **P1-2** | `EmptyState` CTA slots (primary + secondary); audit callers | `src/lib/components/EmptyState.svelte` (+ feeds, export-feeds, campaigns, stores, …) |
| [x] | **P1-3** | Bulk confirm preview (count, credits, irreversible) | `src/lib/components/products/ProductProcessingActions.svelte`, `src/lib/components/categories/formula/ConfirmationDialog.svelte`, products reject/reset + channel overwrite Dialogs |
| [~] | **P1-4** | Channel sync UX (import vs outbound, last-sync summary, overwrite warn) — full conflict/SoT review still open | `src/routes/woocommerce/+page.svelte`, `src/routes/stores/shopify/+page.svelte`, `src/routes/shopify/+page.svelte` |
| [x] | **P1-5** | Role-based chrome (admin vs member: invites, keys, billing, delete, brand kit) | `src/routes/settings/+page.svelte`, `src/lib/components/Nav.svelte`, `src/lib/company-admin.ts` |
| [x] | **P1-6** | Nav Setup vs Operate grouping | `src/lib/components/Nav.svelte` |
| [x] | **P1-7** | Completeness / channel-ready signals on products list (lean heuristics) | `src/lib/components/products/ProductTable.svelte`, `src/lib/components/products/types.ts` |
| [~] | **P1-8** | Soft undo where API allows (job cancel Undo; bulk Accept Undo via `/api/products/reset` for admins; bulk reset/delete restore still open) | Product bulk handlers + `Toaster.svelte` / `toast-state.ts` |
| [x] | **P1-9** | 403 empty states (“ask admin”) distinct from no-data | `EmptyState.svelte`, `ForbiddenEmptyState.svelte`, settings/integrations/admin routes |
## Order
1. P0-2 + P0-1 → 2. P0-5 + P0-4 → 3. P0-3 + P0-6 → 4. P1 role / channel / nav
5. **P0-7 → P0-8 → P0-9** (research) → 6. **P1-10 → P1-11 → P1-12**
7. **P0-10 → P0-11 → P0-12** (cutover) → 8. **P1-13 → P1-14 → P1-15**
9. **P0-13 → P0-14** (integration/API cutover) → 10. **P1-16 → P1-17**
---
## Research addendum (20242026 operator UX) — 2026-08-04
Sources: Flatfile/CSVBox/AppMaster mapping UX; Feednode/IRONFEED/FeedOn feed ops; Refibuy/AKKO NORA/eCommerceInsights AI review; Orbix/SaaSUI command palette + notification prefs; NN/progressive disclosure.
### Theme → codebase (done vs gap)
| Theme | Already in `apps/web` | Covered by backlog | Still missing |
|-------|----------------------|--------------------|---------------|
| Progressive disclosure | Activation checklist + tour; mapping preview empty→filled | P0-1 (shipped), P1-1, P1-6 | Advanced/optional feed+AI settings behind “Advanced”; complexity preference persistence |
| Smart defaults | `suggestMappings` + `?automap=1`; exact/alias auto-apply; fuzzy Confirm gate; GTIN merge | P0-6, P0-7 | Blank-cell SoT copy |
| Inline help | Card/page descriptions | — | Field/channel tooltips (`Tooltip` / help popovers on mapping targets + export specs) |
| Undo | Toast `onClick` for job cancel + admin bulk Accept→reset | P1-8 [~] | Bulk reset/delete restore; AI accept rollback without reset |
| Status chips | `StatusBadge`, `ProductStatusBadge`; mapping confidence chips | P0-3, P0-7, P1-7 | Job next-action chips; post-sync delta chips (added/updated/removed) |
| Guided mapping | Suggest + `MappingPreviewPanel` + `evaluateMappingPreflight` + confidence chips | P0-6, P0-7 | “Issues only” sample filter |
| Conflict resolution | Company GTIN merge setting | P1-4 | Field-level SoT / AI merge vs keep / channel overwrite preview |
| Keyboard shortcuts | ⌘K/Ctrl+K command palette (P1-10); Escape/dialogs | P1-10 | Sync/process/map action entries beyond nav jumps |
| Notification prefs | Local in-app matrix (P0-9 [w]) | P0-9 MVP | Server/email prefs + terminal AI job-done toasts — **waived** for cutover (post-cutover backlog) |
### New P0 (research)
| Status | ID | Item | Targets |
|--------|----|------|---------|
| [x] | **P0-7** | Mapping confidence chips + fuzzy confirm gate (exact/alias auto-apply; fuzzy Confirm; chips on banner/table/preview) | `suggest-mappings.ts`, `feeds/[feedId]/mapping/+page.svelte`, `SchemaMappingTable.svelte`, `MappingPreviewPanel.svelte` |
| [w] | **P0-8** | AI enrichment review queue: before/after, accept / edit / reject (+ optional confidence band) — **MVP closed; proposal API waived** | `processing/+page.svelte`, `products/+page.svelte`, `ProductProcessingActions.svelte`, `ProductEditPanel.svelte`; pipeline → `needs_review` |
| [w] | **P0-9** | Operator alerts prefs: sync fail, AI job done/fail, export fail (email + in-app; defaults on for failures) — **local MVP closed; server/email waived** | `settings/+page.svelte`, `alert-prefs.ts`, `notify.ts`, sync/export call sites |
#### P0-9 notes (2026-08-04)
**Shipped (local-only, safe):**
- Browser `localStorage` prefs (`descrybe.alert-prefs.v1`): failures default **on**, completions default **off**.
- Settings → **Alerts** tab: in-app toggles (`role=switch` for E2E) + email deferred copy.
- `notifySuccess` / `notifyError` / `notifyApiError` gate on optional `alert` kind; untagged toasts (forms, validation) always show.
- Toasts with `onClick` (Undo) are never muted; stack cap (max 3) and durations untouched.
- Wired: feed sync fail/done, sync+sample fail/done, processing start fail, product export fail/done, export-feed refresh fail/done.
**Deferred (post-cutover; see signed waiver):**
- Server-backed notification preference API (cross-device, company-scoped).
- Email channel for the same event matrix.
- Terminal AI job **completed** toasts (no background completion watcher yet; job-start toast stays always-on for Undo).
##### Signed waiver — P0-9 remainder (2026-08-08)
**WAIVER ID:** `UX-P0-9-SERVER-EMAIL-2026-08-08`
**Decision:** Accept cutover / staging GO **without** server-backed alert prefs or email delivery for the operator alert matrix.
**Accepted by:** product cutover gate (per `docs/go-live-checklist.md` § “signed waiver for tenants that do not need those paths”).
**Why deferred (not small/safe now):**
- Server prefs need schema + company/user-scoped API + Settings sync (cross-device) — migration + authz surface.
- Email channel needs templates, preference gating in mail workers, and proven SMTP/Resend (already an independent cutover blocker).
- Terminal AI job-done toasts need a background completion watcher; job-start toasts must stay unmuted for Undo.
**Residual risk:** prefs are browser-local only (lost on new device/clear storage); no email for sync/AI/export failures until SMTP ops + this backlog ship.
**Re-open when:** SMTP/Resend live-proven **and** a tenant requires cross-device or email alerts. Track as post-cutover P1.
#### P0-8 notes (2026-08-04)
**Shipped (incremental, no proposal API):**
- Pipeline upserts processed products as `needs_review` (legacy `processed` still listed via filter alias).
- Needs Review tab + bulk **Accept enrichment** / **Reject (reset)**; edit panel **Review** tab with original vs enriched side-by-side; Accept / Reject / Save.
- Completed jobs on Processing link to `/products?status=needs_review`.
- Accept = PATCH `status=completed`; Reject = existing `/api/products/reset` (admin).
- **Wave 7:** field-level Accept name/description (copy `processed_*` → live fields, stay `needs_review`); Discard resets `processed_*` when original non-empty; keyboard A/R/Alt+1/Alt+2/Ctrl+Enter; Undo toasts on field actions.
**Deferred (full proposal API; see signed waiver):**
- Dedicated enrich-proposal endpoints (pending proposals, confidence bands).
- Per-job product ID list / job-scoped review queue (jobs do not expose product diffs today).
- Soft undo of whole-product Accept beyond admin bulk Accept→reset (restore previous enriched values without reset) — related to P1-8 / research Undo theme.
##### Signed waiver — P0-8 remainder (2026-08-08)
**WAIVER ID:** `UX-P0-8-PROPOSAL-API-2026-08-08`
**Decision:** Accept cutover / staging GO with the **shipped** Needs Review MVP; do **not** block on a dedicated enrich-proposal API, job-scoped review queue, or soft whole-product Accept undo.
**Accepted by:** product cutover gate (per `docs/go-live-checklist.md` § “signed waiver for tenants that do not need those paths”).
**Why deferred (not small/safe now):**
- Proposal API + confidence bands imply new persistence, list/get/accept contracts, and OpenAPI drift — medium blast radius on catalog/processing.
- Job-scoped queues need processing-job → product-diff linkage the job model does not expose today.
- Soft undo of whole-product Accept overlaps P1-8 / research Undo and risks incorrect field restoration without prior-value snapshots.
**Residual risk:** operators review via status filter + field-level accept/discard, not proposal confidence bands or per-job queues; whole-product Accept undo remains admin reset only.
**Re-open when:** a paying tenant requires confidence-gated proposals or job-scoped review, or Undo theme (P1-8) lands snapshots. Track as post-cutover P1.
### New P1 (research)
| Status | ID | Item | Targets |
|--------|----|------|---------|
| [x] | **P1-10** | ⌘K/Ctrl+K command palette (nav + common actions: sync, process, map, jobs) | new `CommandPalette.svelte`, `+layout.svelte`, `Nav.svelte` |
| [~] | **P1-11** | Inline field help (glossary tips on mapping landed; export + blank=keep/overwrite still open) | mapping page, `standard-fields`, export-feed builder |
| [~] | **P1-12** | Soft undo via toast `onClick` (job cancel + bulk Accept Undo for admins; bulk reset/delete still open) | `toast-state.ts`, `Toaster.svelte`, product bulk handlers |
---
## Cutover / production-readiness UX (2026-08-04)
Sources: FileFeed/Spiderhunts SaaS migration + reconciliation; Rework account setup (RBAC/SSO blockers); Gigabit ERP cutover + hypercare; YuSMP/StarterPick/Jimo B2B onboarding (activation event, invite+role, stuck accounts); Auth0 migrated password invite UX; Stripe Billing go-live (webhooks as SoT, past_due grace, Customer Portal); Bitsfolio/SautiKit invite email-mismatch recovery; Helply/Whatfix hypercare exit criteria.
Mapped to Descrybe cutover blockers in `docs/cutover.md` / `docs/migration-readiness.md` (synthetic emails, all-memberships-as-`member`, skipped `company_plans`, set-password after `-skip-post-import`).
### Theme → already covered vs gap
| Theme | Already in backlog / product | Still missing (this addendum) |
|-------|------------------------------|-------------------------------|
| Greenfield activation checklist | P0-1 (shipped) | **P1-13 [x]** Migrated-cohort track (platform cutover + company dashboard gate) |
| Role-based chrome | P1-5 (shipped); **P0-11 role repair [x]** | — |
| Set-password accept flow | `/accept-invite` + login CTA + admin re-issue (P0-10 [x] code) | SMTP/login smoke still operator |
| Billing page / portal | `/billing` + Manage subscription | **P0-12 [x]** missing-plan + past_due Portal CTA |
| Team invites | Settings invite + role at invite time; member role change (P0-11) | Session email ≠ invite email recovery (**P1-14 [x]**) |
| Ops cutover runbook | `docs/cutover.md` (ops); maintenance/read-only banner + platform-admin readiness strip (P1-15) | — |
### New P0 (cutover)
| Status | ID | Item | Targets |
|--------|----|------|--------|
| [x] | **P0-10** | Migrated set-password first-run: login `password_not_set` + CTA → `/accept-invite`; expired-invite / admin re-issue copy (no public self-serve resend); `mode=set-password` → sign-in + skip greenfield tour; **admin re-issue hardened** (rate limits, skip `@legacy.local`, durable invites, Admin Users UI) — SMTP/login smoke still operator | `login/+page.svelte`, `accept-invite/+page.svelte`, `auth` Login / handleLogin, admin set-password send / Users UI |
| [x] | **P0-11** | Membership **role repair** (promote/demote admin): Settings team control + API — required because migrator imports all as `member`; block demoting last admin — landed: `PATCH /api/team/{userID}` + Settings Team Make admin/member | `settings/+page.svelte` team table, `apps/api` team/membership handlers |
| [x] | **P0-12** | Billing go-live / entitlement recovery: banner when plan missing or Free fallback after skipped `company_plans`; admin Checkout CTA + member “ask admin”; `past_due` sticky banner → Customer Portal (grace copy, dont hard-lock day 1) | `billing/+page.svelte`, `billing-display.ts`, `+layout.svelte` / dashboard banner; `cmd/migrator` `-assign-missing-plans` |
#### P0-12 notes (2026-08-04)
**Shipped:**
- API `has_active_plan` on CreditsOverview — missing/skipped `company_plans` no longer looks like intentional Free / Unlimited.
- Stripe `subscription_status` on status + webhook-managed `stripe_status:*` note; `past_due` grace copy (no day-1 hard lock).
- Billing + sticky layout + dashboard recovery banners: admin → Plans / Customer Portal; member → ask admin.
- `formatSkuUsage` / display helpers no longer say Unlimited when no plan is assigned.
- Migrator plan-skip tooling: `-list-companies-without-plans`, `-assign-missing-plans` (never overwrites active plans), ETL `-fallback-plan-name` (see `docs/cutover.md`).
**Deferred:** dedicated `stripe_subscription_status` column (status currently cached in managed `company_plans.notes` prefix).
#### P0-10 notes (2026-08-04)
**Shipped (code):**
- Login returns `403` + `code=password_not_set` for `must_set_password` users; login page CTA → `/accept-invite` + admin re-issue guidance (no public self-serve resend API).
- Accept-invite: expired/invalid copy with admin re-issue path; `mode=set-password` success skips greenfield activation then sign-in → workspace.
- Rate-limited admin set-password send; skips `@legacy.local`; durable invites (`ReissueSetPasswordInvite`); shareable links when SMTP off; Admin Users UI.
**Still open (ops):** SMTP + mailhooks + login smoke remain **operator-owned**. Production cutover remains **NO-GO** (`docs/cutover.md`, `docs/migration-readiness.md`).
### New P1 (cutover)
| Status | ID | Item | Targets |
|--------|----|------|--------|
| [x] | **P1-13** | Migrated-cohort onboarding track (≠ P0-1): platform cutover checklist (verify email → promote admin → set-password → confirm plan → SMTP smoke) on Admin home/Settings; company track on dashboard when gated (set-password accept / `must_set_password`) — skippable, advances from evidence | `MigratedCohortChecklist`, `lib/migrated-cohort/*`, `admin/+page.svelte`, `admin/settings`, `dashboard/+page.svelte`, `accept-invite` |
| [x] | **P1-14** | Invite / set-password **email mismatch** recovery: if session email ≠ invite email, explicit sign-out + switch account (no silent fail); login `password_not_set` copy + admin re-issue path — landed: `POST /api/auth/invite-preview`, accept/complete mismatch `code=email_mismatch`, accept-invite + login UX | `accept-invite/+page.svelte`, `login/+page.svelte`, auth handlers |
| [x] | **P1-15** | Cutover hypercare chrome: sticky maintenance / read-only banner from `/healthz` (**landed** `SystemModeBanner` + client mutate block); platform-admin readiness strip (counts: `must_set_password`, companies without admin, companies without plan) with deep links to Users / Settings (roles) / Billing | `SystemModeBanner.svelte`, `CutoverReadinessBanner.svelte`, `cutover-readiness.svelte.ts`, `+layout.svelte`, `GET /api/admin/readiness` |
---
## Cutover UX gaps addendum (integrations / known ETL skips) — 2026-08-04
Sources: FileFeed SaaS migration (re-establish integrations + post-migration verification); Truto / Paragon reconnect cliff (`needs_reauth` in-app banner, deep-link re-auth, batch reconnect); knowledgelib ERP cutover (credential provisioning lead time); STOA / Refgrow cutover consumer FAQ + support channel; StarterPick stuck-account inspect; Helply hypercare exit criteria. Mapped to explicit Gaps in `docs/cutover.md` / `docs/migration-readiness.md`: `api_keys`, Woo configs, file blobs, jobs/history, full `company_settings` not migrated.
### Theme → already covered vs gap
| Theme | Already in backlog / product | Still missing (this addendum) |
|-------|------------------------------|-------------------------------|
| Channel sync / SoT conflict UX | P1-4 [~]; **P0-13 [x]** reconnect cliff | — |
| Set-password / role / billing go-live | P0-10…P0-12, P1-13…P1-15 | — |
| API keys (greenfield create) | Settings integrations | **P0-14 [x]** cutover empty honesty + Stores/docs deep links (revoke-all deferred) |
| Hypercare admin strip / maintenance | **P1-15 [x]** readiness strip; **P1-16 [x]** known-gap honesty; **P1-17 [x]** report-missing CTA | — |
| Empty jobs / files | Honest empties elsewhere; **P1-16 [x]** migrated-cohort gaps panel | — |
### New P0
| Status | ID | Item | Targets |
|--------|----|------|--------|
| [x] | **P0-13** | Store / connector **reconnect cliff** after cutover: detect missing Woo (and Shopify) credentials/`has_credentials=false` post-migration; sticky admin banner + deep-link to reconnect (not generic Settings); Test Connection before first sync; batch list if multiple stores need reauth — migrator does **not** import usable Woo secrets | `woocommerce/+page.svelte`, `stores/shopify/+page.svelte`, `stores/+page.svelte`, dashboard banner, `StoreReconnectBanner.svelte`, `store-reconnect.ts` |
| [x] | **P0-14** | **API key reissue** for migrated tenants: honest empty + “legacy keys were not migrated” copy; create/rotate with one-time plaintext reveal; optional revoke-all; link from Integrations — `api_keys` not in ETL — **landed:** Settings empty Alert + EmptyState CTA; create dialog one-time reveal; Stores hub + docs/export-feeds deep links to `?tab=api-keys`; revoke-all deferred | Settings API keys UI, `stores/+page.svelte`, docs/export-feeds links |
#### P0-13 notes (2026-08-04)
**Shipped:** `needsStoreReconnect` when config identity exists but `has_credentials=false`; `StoreReconnectBanner` on Woo/Shopify pages, stores hub (batch list), and dashboard; sync CTAs gated until credentials saved; sync error copy reframed as reconnect (not mystery broken sync); deep links to `#store-reconnect` / connection tab.
### New P1
| Status | ID | Item | Targets |
|--------|----|------|--------|
| [x] | **P1-16** | **Known ETL gap honesty** (migrated cohort): dismissible dashboard panel listing what did **not** move (API keys, Woo secrets, file blobs, job/history, partial `company_settings`) with deep links to recreate/reconnect; distinct from P1-13 activation steps — **landed:** `MigratedEtlGapsPanel` + `etl-gaps.ts` (dismiss localStorage); gated on migrated cohort (Legacy/A1 name) or hypercare + admin; links to API keys / stores / files / processing / settings; **admin inventory:** diagnostics `migration_inventory` cheap COUNTs (metadata-only files / migrated jobs / tasks) + Files metadata-only notice — no fake import | Dashboard cutover panel; gate on migrated/session heuristic; `/admin/diagnostics` |
| [x] | **P1-17** | Hypercare **report missing / wrong data** CTA (tenant): reuses support ticket create (`/support/new?category=migration…`) while `HYPERCARE_MODE` is on; pairs with P1-15 admin strip + triage `q=Hypercare`; exit when env cleared — **landed:** `HypercareReportBanner`, `hypercare-report.ts`, healthz `hypercare` | Layout CTA, `/support/new` prefills, admin queue link |
| [x] | **P1-18** | **Self-serve forgot-password** — dedicated reset tokens + opaque `POST /api/auth/forgot-password` + `POST /api/auth/reset-password` + `/forgot-password`/`/reset-password` UI; reuses SMTP/`mail.Send` + `EMAIL_DRY_RUN`. Design: [forgot-password.md](forgot-password.md). Admin set-password re-issue remains for migration cohort | `auth` service/handlers, `internal/mail`, login + forgot/reset routes |