# 09 — Dashboard gating (agent 9/10) User-facing dashboard UI respects resolved plan capabilities (`plan ∩ global`). **Coordinates with:** `01-feature-keys.json`, `03-permission-contract.md`, `05-api.md`. --- ## ASSUMPTIONS 1. **Primary source:** `credits.features` on `GET /api/auth/me` (and `/api/billing/credits`), per contract §8. 2. **Secondary fetch once:** `GET /api/billing/capabilities` from layout after `/me` (full registry + `sections` + etag). Safe if 404/unavailable. 3. **Fail behavior when features map is absent** (pre-cutover or capabilities error without a map): - **Fail-open** for core Free-loop + account/billing/support keys (`CORE_ALWAYS_ON`). - **Fail-closed** for paid keys in `PAID_FAIL_CLOSED` (AI process, campaign AI/send, brand AI apply, SEO AI, BYOK, API keys, …) — matches agent-9 brief while keeping billing/settings reachable. 4. **When features map is present:** enforce map + parent-prefix deny (disabled `catalog.products` hides children). Missing key → false. 5. **Admin shell** (`/admin/*`) is out of scope. 6. **Meters unchanged:** `can_use_ai` / wallet still compose with feature keys for AI actions. --- ## Implementation | Piece | Path | |-------|------| | Helpers + route map | `apps/web/src/lib/plan-capabilities.ts` | | Session store (fetch once) | `apps/web/src/lib/plan-capabilities.svelte.ts` | | `canFeature` re-export | `apps/web/src/lib/plan-gates.ts` | | Types | `CreditBalance.features` / `disabled_features` / `feature_etag` | | Route guard | `PlanRouteGuard.svelte` in dashboard layout | | Upgrade CTA | `PlanUpgradePanel.svelte` / `FeatureGate.svelte` | | Nav filter | `Nav.svelte` primary + More | | Command palette | `CommandPalette.svelte` | | Settings tabs | `settings/+page.svelte` | | Section actions | Products AI, Campaign AI, SEO AI, Brand AI apply, AI BYOK | Hydration: layout `/api/auth/me` → `planCapabilities.hydrateFromCredits` → `planCapabilities.refresh()`. --- ## Gated routes | Path | Feature key | |------|-------------| | `/dashboard` | `dashboard.overview` | | `/products` | `catalog.products` | | `/feeds` | `feeds.list` | | `/feeds/[feedId]/mapping` | `feeds.mapping` | | `/export-feeds` | `feeds.export_feeds` | | `/files` | `feeds.uploads` | | `/stores` | `stores.hub` | | `/woocommerce` | `stores.woocommerce` | | `/woocommerce?tab=reviews` | `marketing.reviews` | | `/stores/shopify`, `/shopify` | `stores.shopify` | | `/processing`, `/tasks` | `processing.monitor` | | `/categories` | `catalog.categories` | | `/categories/[id]/title-formula` | `catalog.categories.title_formula` | | `/categories/[id]/description-formula` | `catalog.categories.description_formula` | | `/attributes` | `catalog.attributes` | | `/standard-fields` | `catalog.standard_fields` | | `/structured-descriptions` | `catalog.structured_descriptions` | | `/vector-categories` | `catalog.vector_categories` | | `/campaigns`, `/campaigns/new`, `/campaigns/[id]` | `marketing.campaigns` | | `/marketing/calendar` | `marketing.content_calendar` | | `/brand` | `marketing.brand_kit` | | `/seo` | `marketing.seo` | | `/integrations/ai` | `integrations.ai` | | `/integrations/email` | `integrations.email` | | `/billing` | `billing.overview` | | `/plans` | `billing.plans_compare` | | `/settings` | `settings.profile` (+ tab keys) | | `/settings?tab=api-keys` | `settings.api_keys` | | `/support` | `support.center` | | `/support/new` | `support.ticket_create` | | `/support/[ticketId]` | `support.ticket_thread` | Deep-link to a disabled route → in-place upgrade panel (not hard redirect). --- ## Section gates (shared pages) | Surface | Keys | |---------|------| | Settings tabs | `settings.profile` / `company` / `alerts` / `api_keys` / `team` | | Products AI actions | `catalog.products.process_ai_*` + `capability.ai_processing` | | Campaign AI / send | `marketing.campaigns.generate_ai` + `capability.campaign_ai` | | SEO AI rewrite | `marketing.seo.ai_rewrite` + `capability.seo_ai_rewrite` | | Brand AI apply banner | `marketing.brand_ai_apply` + `capability.brand_ai_apply` | | AI BYOK modes | `integrations.ai.byok` + `capability.byok` | --- ## Verification - `npm run check` in `apps/web` (svelte-check) - Manual: Free user — API keys tab hidden/upgraded; AI process locked; billing/settings reachable when capabilities 404