Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
13 KiB
UX research: Admin, roles/plans, support desk
Product: Descrybe — product feed / AI catalog SaaS (Go API + SvelteKit admin).
Scope: Clean ops panels, plan/role permission matrices, support queue with assignment + CSAT.
Date: 2026-08-05
Method: Web research (admin tables, RBAC/entitlements, support SLA/CSAT) + codehelper skim of existing admin UI.
Existing Descrybe surface (baseline)
| Area | What exists today | Paths / symbols |
|---|---|---|
| Admin chrome | Fixed left nav, dark shell (.dark), brand “Descrybe” + Admin pill |
AdminNav.svelte |
| Page frame | PageShell → title + description + actions; max-w-7xl |
PageShell.svelte |
| Tables | TableShell = bordered bg-card + scroll; row hover muted/40 |
TableShell.svelte |
| Support queue | Status chip filters, search, Badge status map, click-through rows | /admin/support, listAdminSupportTickets |
| Ticket model | status, priority, category, assignee_admin_user_id, message thread |
support/admin-api.ts |
| Plan features | Package select + section filter + search; checkbox matrix; package Badges | PlanPermissionsPanel.svelte |
| Tokens | Majorelle primary, russian-violet ink, slate-friendly cards; semantic Badge variants | layout.css (--primary 247°, success/warning Badges) |
Implication: Do not invent a second admin visual system. Extend PageShell + Card + TableShell + Badge + filter chips already used on Support. Prefer semantic tokens (border-border, bg-primary/15, text-muted-foreground) over one-off slate-* where the rest of admin already uses tokens (Support does; PlanPermissions still hardcodes some slate).
A) Clean admin / ops panels
Patterns to apply
-
Job-first hierarchy (3 levels)
- L1: entity/queue purpose + critical alerts + primary actions (Refresh, Assign, Resolve).
- L2: filters + operational table.
- L3: audit / history / advanced settings on demand (drawer or sub-route).
Source pattern: SaaS admin hierarchy guides (Taqwah, PyColors).
-
Density as a tool, not a vibe
- Ops tables: comfortable default (~40–48px rows); optional compact for power users later.
- Cap overview KPI cards at 3–5 (admin overview already leans summary + tool grid — keep that restraint).
- Sticky table headers; freeze identity column (subject / company name) when horizontal scroll appears.
-
Queue-shaped tables
Useful traits: strong status, workflow filters, clear row entry, stable column order.
Weak traits: ambiguous state, decorative charts instead of work, actions buried in kebab menus for primary work. -
Filter UX
- Removable filter chips + result count (“12 of 240 · status open · priority high”).
- Global search + a few workflow filters (status / assignee / plan tier / SLA risk) — not a filter drawer per column on day one.
- Persist key filters in URL (
?status=open) — Support already does this; extend to assignee/SLA.
-
Layout zones for ticket / user detail
Entity header (state + plan + SLA) → primary thread/table → side sheet for assignment / meta → audit strip.
Prefer split pane or side sheet over full-page form for assignment so the queue context stays visible.
DO / DON'T — avoiding the “ugly admin” look (Descrybe-native)
| DO | DON'T |
|---|---|
Reuse PageShell, Card, TableShell, Lucide icons at h-4/h-5 |
New dashboard kits, glassmorphism, neon charts, multi-shadow cards |
| Use majorelle sparingly: active nav, selected filter, primary CTA | Purple-to-indigo hero gradients, glow rings, “AI sparkle” chrome on every control |
Semantic status via Badge (warning/success/secondary) |
Rainbow row backgrounds or emoji status columns |
| One composition per page: title → filters → table | KPI strip + chart wall + three card grids above every queue |
| Tabular nums + truncate with tooltip for IDs/emails | Wrapping every cell; monospace walls of UUID |
| Destructive actions visually separate + confirm | Red “Delete” next to “Reply” without consequence copy |
Match admin dark shell tokens from layout.css |
Flat gray Bootstrap admin or cream/serif “editorial” admin |
Brand note: Descrybe’s primary is majorelle blue. “Not purple AI slop” means don’t decorate — use brand as selection/affordance, not as atmosphere.
B) Plan / role permission matrices
Model (separate two axes)
Research consensus (Stripe-style entitlements + B2B RBAC UX):
| Axis | Question | Descrybe mapping |
|---|---|---|
| Role | What can this person do? | Platform admin vs company admin vs member; future scoped ops roles |
| Plan / entitlement | What has this account bought? | Plans + PlanPermissionsPanel feature catalog + feature gates |
| Scope | Where does it apply? | Company / catalog / feed — keep visible if multi-tenant ops act across companies |
| Lifecycle | Trial, past_due, suspended, custom deal | Already surfaced in billing helpers (billing-display, package Badges) |
Never blur “member can export” (role) with “Advanced export is on Enterprise” (plan). UI should explain which layer blocked an action.
Matrix UX patterns
-
Read matrix for audit; edit via focused builder
Full checkbox grids are good for platform ops (current Plan features panel). For end-customer role assignment, prefer named roles + plain-language summary, not a wall of toggles. -
Group by task domain
Sections already inPLAN_FEATURE_SECTIONS— keep section headers, global gate Badge (“Globally disabled”), and search. Add “affected companies / seats” count before bulk enable/disable. -
Honest gating states
Hide vs disable vs upgrade CTA vs “ask admin” — pick per feature. Support/billing should show why (plan vs role vs lifecycle). -
Role chips
Compact chips: plan name,is_custom, trial, platform-admin. UseBadge variant="outline"for ladder defaults andsecondaryfor custom deals (already started in PlanPermissionsPanel). Avoid inventing 8 near-identical admin role names. -
Exceptions expire
Support overrides and custom deals need owner, reason, expiry, audit — otherwise Support becomes the policy engine.
DO / DON'T — permissions UI
| DO | DON'T |
|---|---|
| Label packages in customer language (Free / Pro / Enterprise / Custom deal) | Internal flags as the only label (is_custom alone) |
| Show count enabled / total; section + search filters | Unfiltered 80-row checkbox dump with no scan path |
| Confirm bulk “Disable all” with blast radius | Silent global section flips |
| Log who changed plan features | Toggles with no success/error feedback (panel already alerts — keep it) |
| Separate “AI roles” (provider keys) from user RBAC | Mixing admin-ai-roles (LLM slots) into the human permission matrix naming |
C) Support desk: assignment, queues, SLA, CSAT
Current hooks
- Queue: status filters + search; columns subject/status/priority/company/requester/updated.
- API update already allows
assignee_admin_user_id/clear_assignee. - Priorities:
low | normal | high. Categories:billing | bug | account | other. - Gap vs research: no SLA timers, no assignee column/filter, no CSAT fields, no “my queue” / unassigned views.
Ticket queue patterns
-
Ownership model
Default for B2B SaaS: route by product area / category + customer plan tier, then assign.
Assignment modes to support:- Unassigned pool → claim
- Round-robin / load-balance for general
- Skill / category for billing vs bug
- VIP / Enterprise bypass for high impact
-
Queue views (filters, not separate apps)
- All open · Unassigned · Mine · SLA at risk · Waiting on customer (
pending)
Keep chip style from Support page (border-primary bg-primary/15when active).
- All open · Unassigned · Mine · SLA at risk · Waiting on customer (
-
Row information hierarchy
Primary: subject + company. Secondary: status Badge, priority, assignee avatar/initials, last message age. Tertiary: category under subject (already). Add SLA countdown only when defined. -
SLA as timers + events
- Tier by priority (and optionally plan): first response + resolution targets.
- States: on track → at risk (e.g. 80% elapsed) → breached.
- Pause when waiting on customer.
- UI: subtle text for on-track;
Badge variant="warning"at risk;destructiveonly for breach — not red rows.
-
Assignment UX
- Inline assignee control on detail header; optional bulk assign from queue.
- Show workload (open count) in assignee picker.
- Reassignment reason for misroutes (feeds quality metrics).
CSAT patterns
- One rating question + optional comment (1–5 or Good/Neutral/Bad). Extra questions kill response rate.
- Ask in-thread or embed in resolution email — avoid “click this survey link” as primary path.
- Timing: immediate on resolve for highest response; optional short delay (1–4h) if you need confirmation the fix stuck. Cap frequency (~1 survey / customer / 14–30 days).
- Closed loop: rating ≤2 → auto follow-up task for lead/manager within 24h; do not only score the agent.
- Ops dashboard: weekly CSAT trend, by category and by assignee — not a vanity gauge on every ticket list row (show star only when rated).
DO / DON'T — support UX
| DO | DON'T |
|---|---|
| Explicit assignee + unassigned queue | Tickets that only change status with no owner |
| Concrete priority definitions (impact × urgency) | “High” used for every billing question |
| Pair speed (FRT/SLA) with CSAT / reopen rate | Optimize SLA alone until quality collapses |
| Internal notes distinct from customer replies | Agents accidentally sending notes to customers |
| Macros that sound human; show company plan on ticket | Generic AI purple assist panel auto-replying without gate (support AI role stays off until product gate) |
Concrete patterns checklist (build against this)
| Pattern | Apply on |
|---|---|
| Density | Support + Users + Billing tables via shared table cell padding |
| Hierarchy | PageShell title → chip filters → TableShell → detail |
| Tables | Sticky header; identity first; Badge for state; truncate + title |
| Filters | Chips + URL params + count; search left-icon Input |
| Role / plan chips | Badge outline vs secondary; plan name humanized |
| Ticket queues | Unassigned / Mine / At risk views; assignee column |
| SLA | Timer + at-risk Badge; pause on pending |
| Ratings | Post-resolve 1–5; optional comment; closed-loop on lows |
Top 8 recommendations
-
Standardize ops pages on one skeleton —
PageShell+ filter chip bar +TableShell+ empty/forbidden/unavailable states (copy Support; retire ad-hoc slate-only cards where tokens suffice). -
Ship assignee-centric queues next — column + filter + “Unassigned / Mine”; wire existing
assignee_admin_user_idbefore adding SLA or CSAT chrome. -
Keep plan entitlements and human roles as two panels — extend
PlanPermissionsPanelfor packages; separate company/platform RBAC; never merge withadmin-ai-rolesLLM slots in the same matrix. -
Permission matrix = grouped sections + search + blast-radius confirms — preserve section gates and package Badges; add “who is affected” on enable/disable all.
-
Add SLA as a thin operational layer — first-response + resolution by priority (optionally × plan tier); show at-risk/breach Badges; pause on
pending. -
CSAT: one click in the resolution path — 1–5 (or 3-point) in ticket UI / resolution email; optional comment on low scores only; frequency cap; manager follow-up automation for ≤2.
-
Visual discipline = brand as signal, not decoration — majorelle for selection/CTA only; semantic Badges for state; no gradient dashboards, glow, or decorative KPI walls.
-
Measure ops health with a short metric set — FRT, SLA at-risk/breach, misroute/reassign rate, reopen rate, CSAT weekly by category — surface on Support overview, not as chart spam on the queue.
Sources (web)
- SaaS Admin Panel Design — hierarchy & density
- Admin tables & queues
- Data table density, chips, sticky headers
- Permission matrix as product surface (roles vs entitlements)
- Multi-role B2B SaaS — role / plan / scope
- Support triage, routing, SLA events
- Queue strategy & SLA milestones
- CSAT timing, one-question surveys, closed loop
Open follow-ups (out of scope for this note)
- Exact SLA targets (minutes/hours) per priority × plan — needs ops decision.
- Whether CSAT is in-app only, email-embedded, or both.
- Company-side roles matrix vs platform-admin-only for v1.