# P3-admin-surface — screen inventory

| | |
| --- | --- |
| Title (ar) | واجهة الإدارة |
| Title (en) | The admin surface |
| Owning epic | D3.0 |
| Reviewer | Zayed |
| Timebox | 3 days |
| Opened | 2026-09-18 |
| Reviewed | — |
| Locked | — |
| Status | draft |

## The question

Administration is a handful of screens for a handful of people, and a mistake in
any of them is expensive in a way no other mistake in this product is: reference
data is read by every sign-up form in the platform, a profile claim hands one
person another person's record, and a club approval is the gate the entire
marketplace hangs on.

S1 already settled the **frame**: administration is the application shell with an
administration group, not a world of its own, and a screen that writes reference
data for the whole platform says so, confirms every global or destructive write
with its scope named in the sentence, and keeps a "last edit by, and when"
column. That is locked on 18 September 2026 and this prototype renders it rather
than re-asking it.

What is left, and what P3 is for, is the **inside** of that group:

- S1 shipped two administration entries because it had two administration
  screens. Stage 2 has five, and the sixth — the verification queue — is P6's.
  What happens to a group when it keeps growing?
- Reference data is a list of things you edit. Claims and club approvals are
  queues of decisions you take, each needing a reason when refused. Those are two
  different shapes and the prototype has to say which is which.
- **The review pattern — queue, detail, decision with a reason — is reused
  verbatim by P4's squad-entry exceptions and P6's company verification.** It is
  settled here, once, and copied there. That makes D3 the most consequential
  decision in this prototype and the reason P3 is built before P4, P5 and P6.
- And the part S1 left short: naming a write's scope in the confirmation is
  necessary. Is it sufficient when the write is destructive and global?

## Decisions to settle

| Id | Decision | Options built | Switch |
| --- | --- | --- | --- |
| D1 | Administration has five entries now and will have more. Do its entries become **sub-groups in the main rail**, or does administration become **an area with its own sub-navigation**, as the club already is? | a) sub-groups in the rail — `adminnav=groups` b) an area with a tab strip — `adminnav=area` | `adminnav` |
| D2 | Is a reference-data catalogue a **table with inline actions**, or a **list beside a detail**? | a) a table, every row editable in place — `list=table` b) a list and a detail panel — `list=split` | `list` |
| **D3** | **The review pattern.** A queue of decisions, a detail, and a decision that requires a reason when it refuses. **P4 and P6 reuse whichever answer this is, verbatim.** | a) queue and detail side by side on one screen, the decision taken in place — `review=split` b) a queue that opens a detail **route**, the decision taken there — `review=route` | `review` |
| D4 | S1 settled that a global or destructive write confirms with its scope named in the sentence. Is naming the scope **enough** for a destructive write, or must the person also type the thing's name? | a) the scope named in the sentence — `confirm=named` b) the scope named **and** the name typed back — `confirm=typed` | `confirm` |

### What the reviewer must answer

- **D1** — This is not S2 being reopened. S2 is settled: administration lives in
  the application shell with an administration group, and that is what both
  variants render. The question is what happens **inside** that group as it grows
  — S1 shipped two entries, Stage 2 renders five, and P6 adds a sixth screen the
  five do not reach. Sub-groups keep everything one click away and make
  the main rail longer for the handful of accounts that hold it. An area matches
  what S3 already decided for the club — one entry, a place, a tab strip — and
  costs a click. Answer: which, and if it is an area, whether its tab strip is the
  same component as the club's or a second one.
- **D2** — A catalogue is eight rows today and 195 for nationalities. A table
  with inline actions is fastest for a correction, which is what most edits are;
  a list and detail is better when an item has more to it than fits a row —
  which, once an item has two labels, a sort order, an active flag and a
  provenance, it already does. Answer: which, and at what size the answer changes.
- **D3** — The one that outlives this prototype. Every queue in the product
  eventually looks like this: a list of things waiting on a person, a detail
  with evidence, and a decision that has to carry a reason when it refuses.
  Side by side keeps the queue visible, so a reviewer working seven claims never
  loses their place, and it squeezes the evidence — a document preview — into
  half a screen. A route gives the evidence the whole screen and costs the
  reviewer their place in the queue on every decision. Answer: which, and
  **whether the reason is required before the refusal is possible or collected in
  the confirmation after it** — the prototype renders the first, and the second is
  a one-line change if the reviewer wants it.
- **D4** — Deactivating a catalogue item is the smallest destructive write in
  the product and it has already broken something: «جناح أيسر» was deactivated on
  17 September 2026, still appears on the sign-up form, and a club has an open
  need on that very position with an application against it. Answer: whether
  naming the scope is enough, and — separately — whether a destructive write that
  has live dependents should be refused rather than confirmed.

## Screens

`p3.js` renders the chrome only, and is `p2.js` with this prototype's switches
and its own `HERE`. The navigation list, the grouping, the badge rule and the
absent-not-disabled rule are identical in both files, because they are S1's and
not this prototype's to vary.

| # | Screen | File | Purpose | Actor | Settles |
| --- | --- | --- | --- | --- | --- |
| 1 | Reference data | `index.html` | One catalogue end to end: both labels, sort order, the active flag, and the "last edit by, and when" column S1 requires. The entry screen. | Platform admin | D1, D2, D4 |
| 2 | One catalogue item | `item.html` | The form, and the destructive write with its confirmation in both D4 shapes. | Platform admin | D2, D4 |
| 3 | The registry read page | `registry.html` | What the API returns for one profile type and one sport — the exact payload P2's dynamic form consumes, read-only, with the option count D2 of P2 turns on. | Platform admin | D1 |
| 4 | Claims queue | `claims.html` | Seven profile claims, oldest first, with the document each carries. The queue half of the review pattern. | Platform admin | **D3**, D1 |
| 5 | One claim | `claim.html` | The evidence, the profile being claimed, and approve or refuse with a reason. The detail half, as a route. | Platform admin | **D3** |
| 6 | Club approvals | `clubs.html` | Two clubs awaiting approval — the queue the S1 review found missing, where the navigation said "review queues" and the build had one. Built with **the same pattern as the claims queue**, which is how the pattern proves it transfers. | Platform admin | **D3** |

### The club approval queue is here on purpose

The S1 review listed it as a missing screen: the platform administrator's
landing links to a club queue and `?queue=claims` rendered the profile-claims
queue instead. "Review queues" is plural in the navigation and was singular in
the build. It is built here because this is the prototype that owns review
queues, and because a pattern claimed to be reusable is worth nothing until a
second queue has been built out of it without changing it.

## Switches carried in the URL

| Parameter | Values | Screens | What it changes |
| --- | --- | --- | --- |
| `locale` | `ar` (default), `en` | all | handled by `shared/proto.js` |
| `theme` | `light` (default), `dark` | all | handled by `shared/proto.js` |
| `state` | see below | all | handled by `shared/proto.js` |
| `adminnav` | `groups` (default), `area` | all | **D1**: sub-groups in the rail, or an area with a tab strip |
| `list` | `table` (default), `split` | `index.html`, `item.html` | **D2**: a table with inline actions, or a list beside a detail |
| `review` | `split` (default), `route` | `claims.html`, `claim.html`, `clubs.html` | **D3**: the queue and the detail together, or the detail as a route |
| `confirm` | `named` (default), `typed` | `index.html`, `item.html` | **D4**: the scope named, or the scope named and the name typed back |
| `mobile` | `drawer` (default), `bar` | all | **Inherited from P2's D5.** Not a decision here. It is carried so that P3's screens can be shot at 390 under whatever P2 settles. |
| `role` | `admin` (default here), `club`, `pro`, `agent` | all | Who is signed in, and therefore which entries exist at all. `no-permission` is what a non-admin gets. |

## States per screen

| Screen | default | loading | empty | error | partial | no-permission | What the non-obvious ones are |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `index.html` | ✓ | ✓ | ✓ | ✓ | | ✓ | `empty`: a catalogue with no items, which is what a newly created one looks like; `no-permission`: a club member who opened an administration route — the refused route, where the content region is replaced inside the shell |
| `item.html` | ✓ | ✓ | | ✓ | ✓ | | `partial`: an item saved with its Arabic label and no English one, which is the ordinary half-finished state of a catalogue; `error`: **deactivation refused because the item has live dependents** |
| `registry.html` | ✓ | ✓ | | ✓ | ✓ | | `partial`: a group whose attributes are defined and whose catalogue is empty, so the sign-up form would render a control with nothing in it |
| `claims.html` | ✓ | ✓ | ✓ | ✓ | | ✓ | `empty`: an administrator with nothing queued; `no-permission`: the refused route |
| `claim.html` | ✓ | ✓ | | ✓ | ✓ | | `partial`: a claim already decided, read afterwards, with the decision and its reason on the record; `error`: a decision that could not be recorded, where the reason typed is not lost |
| `clubs.html` | ✓ | ✓ | ✓ | ✓ | | ✓ | the same three as the claims queue, because it is the same pattern |

## The review pattern, written down

This is the part P4 and P6 copy. It is written here in words as well as in HTML,
so that a later prototype can be checked against it rather than against a
screenshot.

1. **A queue is a list of things waiting on a person**, oldest first, each row
   carrying who or what it is about, when it arrived, and how long it has waited.
   Nothing else. No count that is not the length of the queue.
2. **A detail carries the evidence**, the thing being decided about, and the
   record it would change if approved — side by side with what it looks like now.
3. **A decision is two buttons and a reason.** Approve needs no reason. **Refuse
   requires one**, it is free text rather than a code, and the person being
   refused reads it verbatim — so the field says so on the screen.
4. **A decided item stays readable** with its decision, its reason, its reviewer
   and its date. Nothing in a queue is deleted by being decided.
5. **The decision is recorded against a person**, and that person's name appears
   in the "last edit by, and when" column for the rest of the record's life.

## Fixture world

The Stage 2 cast in [`../FIXTURES.md`](../FIXTURES.md), and nothing added:

| | |
| --- | --- |
| Signed in | **هند الشمري** — platform administrator |
| The catalogue | **المراكز — كرة القدم**, eight items, with **«جناح أيسر» deactivated on 17 سبتمبر 2026** and still on the sign-up form |
| Why that matters | نادي صقور الحجاز has an **open need** for «جناح أيسر», and ريم المالكي **applied to it** for ياسر الجهني on 14 سبتمبر 2026. The deactivation is live damage, not a tidy-up. |
| The claims queue | **seven**, oldest four days — ماجد الحربي، هشام العمراني، راشد العتيبي، بدر السبيعي، زياد المنصور، لوكاس بيريرا، مامادو ديوب |
| The claim worked in full | **ماجد الحربي**, submitted 16 سبتمبر 2026 with an identity card, claiming the pre-created profile اتحاد الوسام made on 9 يوليو 2025 — **the same claim P2's `claim.html` shows from the claimant's side** |
| The clubs queue | **two** — نادي واحة الخرج (registered 14 سبتمبر 2026, owner فهد الزهراني) and نادي شواطئ ينبع لكرة السلة (16 سبتمبر 2026) |
| Today | 18 سبتمبر 2026 |

## Out of scope

- The frame. S1 settled that administration is the application shell with an
  administration group, with a band, scoped confirmations and a provenance
  column. All three are rendered here and none is re-asked.
- The sporting catalogue — federations, seasons, age categories, tournaments,
  windows. It is reference data of the same shape and it belongs to P4, which
  owns the screens that consume it.
- Company verification and external-record review. Same pattern, P6's.
- What a platform administrator's landing looks like. S1's, locked.
- Anything with a price. Version one is free for everyone.

## Open questions

- A destructive write with live dependents — «جناح أيسر» — is rendered here as a
  **refusal**, not a confirmation, because the prototype had to pick one to draw.
  Whether that is right is D4's second half and the reviewer may reverse it.
- The registry read page is read-only here. Whether an administrator may edit a
  group's attributes, or only the catalogues they draw from, is not settled and
  changes what the admin API is allowed to write.
- A claim approved hands one account another account's record, including
  contracts a club recorded. Nothing here says what happens to the **duplicate**
  profile the claimant may already have created.

## Not built

Empty at the time of writing.
