# 08 — Admin Permissions UI (agent 8/20) **Owns:** Admin → Billing → **Permissions** tab (`PlanPermissionsPanel` + `admin-plan-permissions` client). **Entry:** `/admin/billing` → Permissions. **Docs home:** [`README.md`](./README.md) (when present) · prior matrix: [`../plan-permissions/`](../plan-permissions/). --- ## PROBLEM Make plan feature editing clean and usable: named profiles (Legacy / Free / Starter…), bulk section toggles, search/filters, clear default vs custom, and one-click Legacy for A1-like plans — wired to real admin APIs (no stub mode). --- ## Features delivered 1. **Plan profiles** — Legacy, Free, Starter, Growth, Business, Enterprise/all-on. Apply writes a full override matrix via `PUT /api/admin/plans/{id}/features`. 2. **Legacy (A1)** — Allowlist nav (Dashboard, Products, Feeds, Export Feeds, Categories, Attributes, Standard Fields, Billing, Settings). `processing.monitor` and stores/marketing/integrations/support extras stay **OFF**. Prominent **Apply Legacy (A1)** when the selected plan is A1-like (`A1*`, name contains `legacy`, or `is_legacy`). 3. **Bulk section toggles** — Per-plan Enable/Disable section (all keys in that section). Global section masters stay in the right rail (optional “+ features” updates global feature-gate rows). 4. **Search / filters** — Text (key or label), section filter, state filter (all / enabled / disabled / differs from plan-name default). 5. **Default vs custom** — Badges: Legacy / Default ladder / Custom deal; **Defaults** vs **Custom overrides**; profile chip shows ✓ when the resolved matrix matches a named profile; **Clear overrides** sends `{}`. 6. **Real APIs** — No 404 stub / in-session-only path. Mutations use feature endpoints; errors surface in alerts. --- ## Source files | File | Intent | |------|--------| | `apps/web/src/lib/components/admin/PlanPermissionsPanel.svelte` | Permissions checklist UI | | `apps/web/src/lib/admin-plan-permissions.ts` | Profiles, Legacy detect, API client | | `apps/web/src/lib/plan-feature-catalog.ts` | Catalog / sections (shared) | | `apps/web/src/routes/admin/billing/+page.svelte` | Hosts Permissions tab | --- ## API contract used | Method | Path | Use | |--------|------|-----| | `GET` | `/api/admin/plans` | List packages | | `GET\|PUT` | `/api/admin/plans/{id}/features` | Load / replace overrides | | `POST` | `/api/admin/plans/{id}/features/enable-all` | Full ON | | `POST` | `/api/admin/plans/{id}/features/disable-all` | Full OFF | | `GET\|PUT` | `/api/admin/feature-gates` | Global masters | | `PUT` | `/api/admin/feature-gates/sections/{section}` | Global section switch | Auth: session + platform admin + CSRF. --- ## Legacy allowlist (summary) Canonical ON set matches [`03-roles-matrix.md`](./03-roles-matrix.md) / `legacy_user` — including AI titles/descriptions, API keys, and `capability.ai_processing` / `api_access`. Shell skips `shell.support_notifications`; dashboard skips `dashboard.store_reconnect`. OFF (among others): `processing.monitor`, all `stores.*`, all `marketing.*`, integrations, support tickets, campaign/email/brand/SEO/BYOK capabilities. Machine source: `LEGACY_FEATURE_ALLOWLIST` in `admin-plan-permissions.ts`. Backend seed (agent 5) should align; UI Apply Legacy persists even if seed lags. --- ## Visual language Uses admin shell tokens (`border-border`, `bg-card`, `text-muted-foreground`, `bg-primary/15` active chips) — same vocabulary as `AdminNav` / agent 7 shell — not hard-coded slate cards. --- ## Manual verify 1. Platform admin → `/admin/billing` → **Permissions**. 2. Select **Free** → profile chip should show Free ✓ (or Apply Free). Toggle one key → **Custom overrides** + Differs badge. 3. Select **A1** (or create an A1-named deal) → **Apply Legacy (A1)** → `processing.monitor` off; Products/Feeds on. Reload → persisted. 4. Section **Enable section** / **Disable section** on Catalog for the selected plan only. 5. Flip a **Global section** switch; confirm tenant capabilities respect it (agent 9/16). 6. **Clear overrides** → Defaults badge; resolved view matches plan-name defaults (Legacy-like names still resolve Legacy on the client when empty). --- ## Coordination | Agent | Touchpoint | |-------|------------| | 3 / 4 | Roles matrix + contract — profiles follow Legacy allow-nav | | 5 | Backend Legacy seed / `is_legacy` — UI already detects A1 + `is_legacy` | | 7 | Admin shell tokens — Permissions uses the same | | 9 | Plans table → Edit features deep-link (unchanged prop `selectedPlanId`) | | 16 | User nav gating consumes capabilities after Legacy apply | | 19 | Defaults alignment — keep `LEGACY_FEATURE_ALLOWLIST` in sync with seeds | --- ## ASSUMPTIONS 1. **ASSUMPTION:** Empty `plans.features` means “use defaults”; Apply Profile always writes an explicit full map. 2. **ASSUMPTION:** Until agent 5 seeds Legacy on the API, A1 empty overrides may resolve all-ON server-side; **Apply Legacy** is the reliable admin path. 3. Global “Off + features” updates **global feature masters**, not every plan’s overrides.