Skip to content

Admin Panel MVP, round 1: staff sign-in, Access control, Users, Leads and Overview ​

Purpose. This round corrects the approved Admin Panel design before any of it is built. It applies the owner's decisions of 2026-09-28 and closes every gap the implementation assessment measured. Nothing here is built until the returned design is approved.

  • Status: sent and returned 2026-09-28. Its eight items came back mostly resolved; what is still open is round 2, with a verdict per item (QRS-1450). Drafted 2026-09-28. The assessment behind it is Admin Panel MVP assessment; the programme row is QRS-1404.
  • Companion prompt, sent in the same round: Account holds, round 1. It covers what a suspended or blocked account looks like to the public and to the account holder, which lives on other surfaces.
  • Rule for every later round: any gap found during implementation comes back here as round N+1, is approved, and only then is built. The loop is in the assessment's design-gap loop section.

What was measured, and why each item is a design question ​

Pulled fresh on 2026-09-28 with DesignSync list_files and get_file:

  • prototype/admin-panel/Overview.dc.html, Users.dc.html, RBAC.dc.html, Leads.dc.html and admin-shell.js;
  • prototype/platform/users-core.js, segments.js, desk-records.js, ops-signals.js, capabilities.js and leads-core.js;
  • SCREENS.md.

These were read mechanically rather than from memory.

⚠ Only admin-shell.js, RBAC.dc.html, Users.dc.html, Leads.dc.html, leads-core.js and SCREENS.md were kept on disk. Overview.dc.html, users-core.js, segments.js, desk-records.js, ops-signals.js and capabilities.js were read in full in the authoring session and not saved, so claims about them cannot be re-checked from a local copy. Every file is pulled fresh again before its screen's parity contract (the pull-design skill).

#What the design says today (evidence)VerdictTracker
1There is no sign-in screen. Sign-in is signInModal() inside admin-shell.js, drawn over whichever desk was opened. Absent: the set-password link flow, the server outcomes (wrong credentials, no active role, deactivated, too many attempts, network), the submitting state, session expired, signed out elsewhere. The sign-in hint says "at least six characters" while the change-password rules require ten characters, a number, mixed case and a symbol (PW_RULES). A demo role picker sits inside the sign-in flow, and a "Back to the prototype hub" link sits in the profile menu (admin-shell.js:479).Design needed, first priorityQRS-1405
2The staff lifecycle is claimed, not designed. The sign-in copy says accounts are created and passwords reset "in Access control". RBAC.dc.html has no invite, reset, deactivate or offboard flow. "Assign role" takes a free-text full name.Design neededQRS-1406
3Four role models disagree. admin-shell.js ROLES has 7 roles with desk lists. RBAC.dc.html SYSROLES has 10 roles with a module × action matrix. Users.dc.html perms() hardcodes permissions from the demo toggle. leads-core.js ROLES has 5 CRM roles, 11 actions and an own record scope. RBAC MODULES has no users module; crm lacks assign, send, fields, segments and team. Lead owners are the literal OWNERS list.Design neededQRS-1407
4Platform staff are mixed with tenant access. RBAC SCOPES include org and workspace, an External Partner agency role, and assignment targets such as a merchant's workspace.Decided: platform staff onlyQRS-1408
5There is no "not built yet" state for unbuilt desks. The Overview reads one aggregate, ops-signals.js, which spans all fourteen desks (it imports eight platform cores and takes the user directory as an injected module), and the MVP builds four desks. Users' record chain and actions and Leads' Send / Consent / Segments open desks that will not exist. capabilities.js already has the right pattern for operator surfaces (the roles and contracts capabilities: a reserved panel naming the store, no disabled control, no date).Design needed, extending an existing patternQRS-1409
6Leads import is a toast ("reuses the audience importer", which belongs to an out-of-scope desk). Won ("signed up and card published") has no step linking the lead to the account it produced.Design neededQRS-1411
7Copy and controls: the read-only banners name "Ops" while the read-only role is Auditor; assignment dates are free-text inputs; "Reset password or OTP" does not fit people who sign in with a WhatsApp code; "Two key" erase has no approver surface.CorrectionsQRS-1412

Deliberately NOT in this round:

  • View as user. The spike measured that a read-only impersonated session cannot be built on this platform (QRS-1418): Dev signs tokens with ES256, and the only supported path creates a full, writable session. The owner decided on 2026-09-28 that it becomes a read-only support view inside the admin panel, with its own ADR (ADR-0038, reserved) and its own later design round. It is not part of this round.
  • Anything that sends a message. Communications is out of scope and the platform has no consent store (Leads is pipeline only by owner decision).
  • Editing platform policies (Mission Control is out of scope).
  • The handbook site itself. It is a documentation site on the shared portal theme. Only its entry points in the shell are in this round.

The prompt ​

Paste Block 1 and Block 2 together, followed by the PDPR Design Prompt, as the screen coverage mandate directs.

Block 1: the product you are extending ​

text
YOU ARE EXTENDING AN EXISTING APPLICATION, NOT BUILDING A NEW ONE.

This round corrects the QR setu Admin Panel that already exists in this project, in
prototype/admin-panel/. It is the internal console QR setu's own staff use to run the platform:
the Overview and fourteen desks behind one shell. A staff member must not be able to tell where
the existing panel ends and this round's work begins.

STUDY THESE BEFORE PROPOSING ANYTHING. They are the product you are extending.
  prototype/admin-panel/admin-shell.js       the chrome every desk loads: nav groups, header, palette,
                                             sign-in modal, change password, deny screen, breakpoints
  prototype/admin-panel/Overview.dc.html     the command centre, a pure reader of ops-signals.js
  prototype/admin-panel/RBAC.dc.html         Access control: roles, permissions, assignments,
                                             approvals, audit log
  prototype/admin-panel/Users.dc.html        the people and accounts desk
  prototype/admin-panel/Leads.dc.html        Leads & CRM, the sales pipeline the Ops team runs
  prototype/admin-panel/ds-base.js           the shared base every admin screen imports
  prototype/admin-panel/support.js           the runtime every admin screen loads
  prototype/admin-panel/confirm-delete.js    the destructive-action confirm pattern
  prototype/admin-panel/qr-toast.js          the toast pattern
  prototype/admin-panel/leads.spec.md        the Leads model and its one rule
  prototype/platform/users-core.js           the user ecosystem model and its action registry
  prototype/platform/leads-core.js           the lead model, stages, roles and contact policy
  prototype/platform/ops-signals.js          the only cross-desk aggregate
  prototype/platform/desk-records.js         records the desks work on
  prototype/platform/capabilities.js         what the platform holds yet, and the absent state
  prototype/platform/segments.js             saved views and segment rules
  prototype/platform/controls.js             platform policies as data
  SCREENS.md                                 the registry; update every row you change

BEFORE ANY SCREEN, PRODUCE TWO LISTS.
  REUSED  every existing component, module, pattern, token and layout rule you will use
          unchanged, naming the file.
  NEW     anything you must introduce, each with one sentence saying why the existing
          pattern genuinely cannot carry it. A new pattern with no justification is a defect.

EVERY NEW SCREEN MUST HAVE A NAMED RELATIONSHIP to an existing screen, navigation path, user
state or workflow. Stay inside prototype/admin-panel/ and prototype/platform/. Import the shared
modules above by name; a new file beside them inherits nothing unless you say so.

Only introduce a new pattern when the use case genuinely requires it, and say why.

Block 2: this round ​

text
ADMIN PANEL, ROUND 1. THE DECISIONS BELOW ARE SETTLED BY THE OWNER. DESIGN TO THEM; DO NOT
REOPEN THEM.

SETTLED FACTS
  - The panel lives at its own address, admin.qrsetu.com, separate from the public site.
  - Before the panel's own sign-in, a network gate verifies the person's work identity. That
    gate is a Cloudflare page and is NOT part of this design.
  - Staff sign in with WORK EMAIL AND PASSWORD. There is no sign up, no phone sign-in and no
    second factor. Customers and merchants sign in with a WhatsApp code; staff never do.
  - Nobody is ever emailed a password. A new staff member, and anyone whose password is reset,
    receives an email with a single-use, expiring link that opens a Set your password screen.
  - Access control covers QR SETU STAFF ONLY. Organization and workspace scopes and the
    External Partner role are removed from this desk (they belong to a later enterprise track).
  - Four desks are in scope (Overview, Users, Leads & CRM, Access control), plus the shell and
    sign-in. The other eleven stay in the nav but are not built yet.
  - Only a SUPER ADMIN manages staff accounts (invite, reset, deactivate). The design's own
    role note says a Platform Administrator "cannot manage other admins", so wherever the
    copy says "ask a Super Admin or Platform Administrator", it becomes "ask a Super Admin".
  - Leads & CRM is a pipeline only: it records, assigns and tracks. It sends no WhatsApp and
    captures no consent in this release.
  - One password rule everywhere a password is set: at least ten characters, a number, an upper
    and a lower case letter, and a symbol. Sign-in itself only checks the fields are filled.

1. STAFF SIGN-IN, THE FIRST SCREEN. Make it a standalone page, not a modal over a desk; nothing
   of the panel renders behind it. Suggested: prototype/admin-panel/SignIn.dc.html. Reuse the
   brand block and field styling already in signInModal().
   States to design, each one visible:
     default; submitting; wrong email or password (one generic message that never says which);
     too many attempts (wait and try again later; show a time only if the service gives one);
     no active role (the person signed in but holds no current assignment, or it expired: say
     so and say who can grant access); access removed (the staff account was deactivated);
     network or service unavailable (retry);
     session ended: ONE state for every reason a session can end (it timed out, they were
     signed out everywhere, or their password changed), because the app cannot tell which.
     Signing in again returns them to the desk they were on.
   Remove from the shipped panel: the "Continue as" role picker in sign-in, and "Back to the
   prototype hub" in the profile menu and the sidebar footer.
   The prototype still needs to preview roles, so move role choice to a design-tool prop, the
   way Users.dc.html exposes demoState.
   Keep and align: change your password (profile menu), sign out, and the deny screen for a desk
   outside the role.

2. SET YOUR PASSWORD. The screen a staff member reaches from an invite or reset email.
   Suggested: prototype/admin-panel/SetPassword.dc.html.
   States: first time (invite, greeting by name, the role they were given); reset; link no
   longer valid (ONE state: a used link and an expired link cannot be told apart, so do not
   design two; say to ask a Super Admin for a new one); success (then
   straight into the panel, no second sign-in). The four rules checked live, as in the current
   change-password modal.

3. STAFF ACCOUNTS INSIDE ACCESS CONTROL. The lifecycle the sign-in copy already promises:
   invite (work email, name, role, permanent or time-bound, a note); pending invite (sent,
   expires, resend, revoke); active; deactivated; reactivate (sends a fresh set-password link).
   Actions per person: send a password reset link; sign out everywhere; change role;
   deactivate with a required reason. Deactivating someone who owns leads must first reassign
   those leads, inside the same flow.
   Show these refusals in the "not available here" style, each with its sentence:
     you cannot change your own access; the last Super Admin cannot be removed or demoted; only
     a Super Admin grants Super Admin or Platform Administrator; a Super Admin assignment is
     always permanent, never time-bound; invites go only to the work
     email domains the network gate admits (today qrsetu.com); an email that already belongs to
     a QR setu account cannot be invited, so a staff member needs a separate work email.
   "Assign role" picks a real staff member from this list, never a typed name. Its start and
   end dates use the in-app picker pattern (see prototype/mobile-console/future-date-picker.js),
   never a text input.
   A person may hold more than one assignment. Show their effective access as the union, with
   each assignment's own window.

4. ONE ROLE MODEL. Today the shell, Access control, Users and Leads each carry their own. Merge
   them into Access control's module x action matrix and make it the only source:
     - The shell shows a desk only when the role holds view on its module. canOpen() is derived
       from the matrix, never a separate desk list.
     - Add a users module whose actions match what the Users desk offers in this release:
       view, reveal contact details, suspend and reactivate, block and unblock, sign out
       everywhere. (Export is reserved in this release.) Name the permission a refusal needs,
       as users-core already does.
     - Extend crm with the actions Leads uses: assign, manage fields, team view, import, export,
       plus a record scope "own leads only" for sales staff.
     - Users.perms() and the Leads role prop read the signed-in role; nothing hardcodes access.
     - One list of system roles, platform scope only. It includes Sales Manager and Sales
       Executive. External Partner is removed. Every role's grants are visible in the matrix.
     - Lead owners are staff members who hold a CRM role, picked from the staff list.
   In this release the system roles are READ ONLY. Creating, cloning, inheriting or editing a
   role, bulk retire and the Approvals queue keep their places as reserved panels (see item 5).

5. NOT BUILT YET, ONE PRESENTATION. Extend capabilities.js with an operator capability for each
   desk that is not built in this release (Billing, Subscriptions, Moderation, Communications,
   Templates, Affiliates, Ad Manager, Trending, Mission Control, Plans, Workspaces), plus role
   editing and approvals, lead messaging and consent, and user segments. Then use its existing
   reserved-panel rule everywhere a screen touches one of them:
     - Overview: every vital, attention row, map tile, approval count and health row whose desk
       is not built becomes a reserved tile naming what is missing. Never a zero, never a guess.
     - Users record: chain links and actions that open an unbuilt desk.
     - Leads: Send WhatsApp, Ask for consent, Segments, campaign readiness and reachable for
       marketing.
     - Nav: unbuilt desks stay findable and say "not ready yet". No dead link, no fake screen.
   Compose each screen so its release state reads as finished, not as a page of holes.

6. WHAT IS LIVE, SO YOU KNOW WHAT TO DRAW AS REAL.
   USERS
     - Directory: search, facets, sorts and paging.
     - Record: identity, status with the hold reason and who set it, workspaces and their role,
       Setu Card status, plan, order count, and last sign-in time (a login fact, kept separate
       from activity, as the design already insists).
     - Contact details are masked until someone with the reveal permission reveals them, and
       every reveal is logged.
     - Pulse: registered people, the population split (merchant people, individuals, consumers,
       unconverted leads from Leads, staff), paying accounts, new this month.
     - Audit tab.
     - Actions: suspend and reactivate, block and unblock, sign out everywhere. Each asks for a
       reason and writes an audit row.
     - Statuses: Active, Suspended, Blocked, and Deleted by the user.
   NOT LIVE (reserved): lifecycle bands, Cohorts, Churn, Interventions, Segments, acquisition
     source, identity verification, message, plan change, transfer, export and erase.
     "View as user" becomes a read-only support view inside this panel, designed in a later
     round: keep a reserved row for it on the record.
   A hold names WHAT is held: the person, their business, or both (both is the default for a
     solo merchant). A hold on the person refuses their sign-in and ends every session. A hold
     on the business withdraws its Setu Card and public pages and refuses its changes, while its
     team can still sign in. The action sheet asks which, and its copy follows the choice.
   Change the suspend and block copy to what this release does, per that choice, and on
     reactivation everything returns to how it was. Do not promise payments stop.
   The reason a staff member types is kept on QR setu's record and the audit log. The account
     holder is NOT shown it in this release; they see one general message. Say that plainly on
     the action sheet, where the reason is typed.
   A consumer's record shows account facts only, never biodata or chat contents.
   LEADS: the whole pipeline is live.
     - Overview, Today, Call list and the outcome sheet, List and bulk stage and assign,
       Pipeline, Team, Fields, the stage log, lost reasons, duplicates, assignment rules with a
       dry run, and masked phone with audited reveal.
     - Design an IMPORT flow inside Leads: upload a spreadsheet, map columns to the field
       registry, validate with a verdict per row and its original line number, flag duplicates
       within the file and against existing leads, preview, commit, then a result summary.
     - Moving to WON asks which account the lead became. Search by business name or card address,
       and confirm. It is never matched automatically by phone number. Without a link the lead
       cannot be marked Won.
     - Industry options come from the platform's industry list, not a literal in the file.
     - The eight stages stay exactly as designed.
     - CRM policies (weekly cap, quiet days and the rest) show their values read only.
   OVERVIEW: live signals come only from the desks in this release.
     - Staff invites about to expire and assignments ending within 7 days (Access control).
     - Overdue follow-ups, unassigned leads and stalled leads per stage (Leads).
     - Accounts on hold (Users).
     - Workspaces, published Setu Cards and registered people as vitals.
     - Sign-in code delivery over WhatsApp as a health row.
     - The feed from the audit log.
     Everything else is reserved (item 5).

7. SHELL ENTRY POINTS FOR THE OPERATIONS HANDBOOK. The panel carries a read-only handbook at
   admin.qrsetu.com/handbook, written for the operations team. Add a Handbook entry to the shell
   and a "How this desk works" link on each desk that opens that desk's page. Design only the
   entry points; the handbook site itself is not part of this round.

8. COPY AND CONTROLS.
     - The read-only banners name the Auditor role.
     - The Access control audit log shows a system row "auto-expired time-bound role". An
       assignment's end is worked out when access is checked, so nothing is written at the
       moment it ends: show expiry as a derived line, never as a recorded event.
     - Leads: STALL_DAYS and TERMINAL in leads-core.js are keyed by stage label. Key them by
       stage id, so renaming a stage cannot break the funnel.
     - "Reset password or OTP" does not apply to people who sign in with a WhatsApp code. Leave
       it out of this release's Users actions.
     - "Two key" erase is reserved, so no approver surface is needed yet.
     - No em dash or en dash in any user-facing copy.

DELIVERABLES
  - SignIn and SetPassword as new artboards.
  - admin-shell.js, RBAC.dc.html, Users.dc.html, Leads.dc.html and Overview.dc.html revised in
    place (keep a _v1 copy where you rebuild).
  - capabilities.js, users-core.js, leads-core.js and ops-signals.js updated so every screen
    reads one model.
  - SCREENS.md rows updated.
  - The REUSED and NEW lists first. Then, for every item 1 to 8, a line saying where it now
    lives and which states it shows.

After it comes back ​

  1. The owner says it was passed. Then pull fresh (list_files, then get_file for every changed file) into the scratchpad and diff it against this round's pull after CRLF normalisation.
  2. Give a verdict per item 1 to 8: resolved · still open · new gap. Anything not resolved opens round 2. It is never quietly accepted.
  3. Once the owner approves: write the parity contracts (design-system/parity-contracts/admin-*.json, QRS-1420), update the design review pages under design-system/screen-reviews/admin-panel/, and only then start increment 1.

Cross-references ​