# P2-onboarding-profile — screen inventory

| | |
| --- | --- |
| Title (ar) | التسجيل والملف الشخصي |
| Title (en) | Onboarding and profile |
| Owning epic | D2.0 |
| Reviewer | Raheem takes the decisions; Zayed records them and locks |
| Timebox | 3 days |
| Opened | 2026-09-18 |
| Reviewed | — |
| Locked | — |
| Status | draft |

## The question

Everything before this prototype assumes a person already has an account. P2 is
the first screen a stranger sees and the last screen a member ever changes, and
between those two it carries more form than the rest of the product put
together: a registry-driven sign-up whose fields the API decides at runtime, a
profile the owner edits for the life of the account, a mandate that routes every
approach that will ever reach him, and the account's own security.

Three things make it the right place to be first in Stage 2.

**It carries the most chrome at the smallest size.** The S1 review refused to
settle mobile navigation, and said so in terms: at 390 both of S1's variants
collapse to the same ungrouped horizontal scroller with about three and a half
of the list visible, and the grouping — the one thing that made a long list
readable at 1280 — is gone. P2's screens are the hardest case, because a
six-group form with a sticky primary action is exactly where a navigation
pattern either survives or eats the screen. So P2 settles it, once, and P3 to P6
inherit the answer rather than each inventing one.

**Its forms are the pattern every other form copies.** The registry decides the
fields, so the form cannot be hand-drawn; the question is what a control looks
like per data type and what happens when a catalogue has forty values instead of
five. Every later form — a club registering, a need being published, an offer
being countered — is a smaller version of this one.

**Three screens with build tickets have no prototyping ticket at all**, and all
three are the account's own: the auth surface, account and security settings,
and the notifications inbox. They are folded in here, with decisions of their
own, because the alternative is that they get built without ever being drawn.

What this prototype does **not** reopen: the navigation entries, the landing,
the club as an area, the shape of an approach, the refusal pattern, and the
absence of a role switcher. Those are S1's, locked on 18 September 2026, and
this prototype renders them. `P1-market-journey` is likewise locked and its
market screens are linked, never rebuilt.

## Decisions to settle

`D1` to `D5` are the decisions the owning epic names. `D6` to `D8` belong to the
three screens folded in from build tickets that have no prototyping ticket of
their own; each of them is a real question with two answers that are both built.

| Id | Decision | Options built | Switch |
| --- | --- | --- | --- |
| D1 | Is the registry-driven profile form one long page with the groups as sections, or one step per registry group with a progress rail? | a) one step per group, with a progress rail — `form=steps` b) one long page — `form=long` | `form` |
| D2 | What decides the control a registry attribute renders as — its data type alone, or its data type and how many options the catalogue holds? | a) the count decides too: a catalogue over a threshold becomes a searchable field, multi-select becomes chips — `control=rich` b) the data type alone decides, and a catalogue of any size is a native select — `control=plain` | `control` |
| D3 | Where is the claim search offered, so a person does not create a duplicate of a profile the platform already holds? | a) inside the flow, as a step that searches before anything is created — `claim=inline` b) as a second path from the first screen — "I already have a profile" — `claim=separate` | `claim` |
| D4 | What does an agent open her own area on — her clients, or the work addressed to her? | a) the clients list, with the work owed as a band above it — `dash=clients` b) the inbox of work addressed to her, with the clients as a panel beside it — `dash=inbox` | `dash` |
| **D5** | **Mobile navigation at 390, deferred to here by the S1 review and settled once for all of Stage 2.** A grouped list — nineteen entries across six groups, of which a club holds nine and a professional seven — has to reach a 390px screen without becoming an ungrouped scroller. | a) **a drawer from the header**: one menu button, the rail's grouped list in full, nothing privileged — `mobile=drawer` b) **a fixed bottom bar of four**, with the same drawer behind "more" — `mobile=bar` | `mobile` |
| D6 | An expired session is not a refused route and not a failed request. Is it a refusal in place, with the screen and its unsaved work still underneath, or a route of its own that signs you in and returns you? | a) in place, over the screen, the work intact — `session=inline` b) a route of its own with a return address — `session=route` | `session` |
| D7 | Is "my account" one page with sections, or an area with a tab strip like the club? | a) one page, sections in order — `settings=sections` b) a tab strip, one tab per concern — `settings=tabs` | `settings` |
| D8 | Is the notifications inbox one reverse-chronological list, or grouped by what happened? And does a notification carry the action it is about, given the landing already owns "the work waiting on you"? | a) grouped by kind, with the action on the row — `inbox=grouped` b) one flat list, newest first, with a filter — `inbox=flat` | `inbox` |

### What the reviewer must answer

- **D1** — The registry returns the groups and the fields; it does not return a
  layout. Steps make a long form finishable and give the person a place to stop,
  and they cost a route per group and a save per step. One page is one save, one
  scroll and one validation summary, and it is the shape a person who is editing
  rather than registering actually wants. Answer: which one at sign-up, and
  whether **editing** the same data later uses the same shape or the other one.
  The prototype deliberately renders sign-up in both and profile editing as one
  page, so the second half of the question has a screen.
- **D2** — «المراكز — كرة القدم» holds eight values and «الجنسيات» holds
  hundreds, and both are the same registry data type. If the data type alone
  decides, the nationality field is a select of hundreds. If the count decides
  too, the API has to say how many, or the client has to ask. Answer: whether the
  registry spec carries an option count — which changes the payload of the
  onboarding endpoint before it is built — and where the threshold sits.
- **D3** — A duplicate profile is the most expensive mistake in this product:
  it splits a person's history and it is repaired by a queue a platform
  administrator works by hand. Offering the claim search as a second path asks
  a stranger to know that the platform might already hold him. Offering it inside
  the flow asks everyone to sit through a search that most will not need. Answer:
  which, and whether a **match found** stops the flow or merely warns.
- **D4** — An agent is the only actor whose work is entirely about other
  people. The clients list answers "who do I represent"; the inbox answers "what
  do I owe". S1 settled that the **landing** is the work waiting on you, for
  every role — so the question here is not what she lands on, it is what her own
  area is for once the landing has already shown her the work.
- **D5** — **The one this prototype exists to settle for the other four.** At
  390 the drawer keeps the groups and costs a tap on every navigation. The bottom
  bar makes the two or three most-used destinations one tap and keeps the rest in
  the same drawer. Three things the reviewer should weigh, all of them visible in
  the screenshots: a bottom bar and a sticky primary action on a form compete for
  the same strip of screen; the bar's obvious four are the two entries that
  already carry badges plus home, so it mostly duplicates a signal the chrome
  already has; and the four are **not the same four for every role**, which means
  a bar is either per-role — which is a role switcher wearing a different coat —
  or it privileges entries some accounts do not hold. The prototype proposes the
  drawer and builds both.
- **D6** — Session expiry is the only refusal in the product that is nobody's
  fault and costs the person work they have already done. S1's rule covers a
  refused route: replace the content region, keep the shell. An expired session
  is not that — the route is fine and the person is entitled to it. Answer:
  whether the S1 pattern stretches, or whether this is the one case that earns a
  route, and what happens to a half-typed form either way.
- **D7** — The account holds four unrelated things: who I am, how I sign in,
  what the product looks like, and what it is allowed to send me. Sections make
  them one scroll and one mental object; tabs make each of them a place with an
  address, which matters when a support conversation has to say "open this".
  Answer: which, and whether it must be the **same** answer as the club area,
  which S1 settled as a tab strip.
- **D8** — Grouping tells a reader what kind of thing happened before it tells
  him what happened; a flat list tells him the order. The harder half: S1
  settled that the landing is the work waiting on you, so if the inbox also
  carries actions there are two screens that both claim to be where work is done.
  Answer: whether a notification is a **record that something happened** with a
  link to the place the action lives, or an action in its own right.

## Screens

Every file is in this folder. `p2.js` renders the chrome only — the header, the
navigation in whichever D5 shape is selected, the account menu, the notification
badge, the variant bar, and the onboarding progress rail — so the shell is
authored once and every screen inherits it. Each screen's own content is
hand-authored HTML with the Arabic and the English on the element.

| # | Screen | File | Purpose | Actor | Settles |
| --- | --- | --- | --- | --- | --- |
| 1 | Register | `index.html` | Sign-up on the two identity keys, with the claim search offered in whichever D3 place is selected. The entry screen. | Stranger | D3, D5 |
| 2 | Verify | `verify.html` | Confirming the email and the phone, and what happens when a code is wrong, expired or never arrives. | Stranger | D5 |
| 3 | Role and sport | `role.html` | Which profile type, and which of the two sports. The choice that decides which registry groups the next screen asks for. | Stranger | D5 |
| 4 | The registry form | `form.html` | The three-layer dynamic form, in both D1 shapes, with every registry data type rendered in both D2 control sets. The biggest screen in the prototype. | Stranger | D1, D2, D5 |
| 5 | Done | `done.html` | What a finished sign-up is worth, and what is still missing before the profile appears in the market. | New member | D5 |
| 6 | Claim a profile | `claim.html` | Searching for a profile the platform already holds, claiming it with a document, and reading a claim's status while a platform administrator works it. | Stranger, new member | D3 |
| 7 | My profile | `profile.html` | Editing the same registry data after sign-up, plus availability, media, and the fields the owner may **not** edit because a club or the platform owns them. | Professional | D1, D2 |
| 8 | Mandate | `mandate.html` | An agent asking to represent someone, and the represented person answering. The confirmation the S1 review found missing. | Agent, professional | D4 |
| 9 | Agent area | `agent.html` | Clients by mandate state and the work addressed to her, in both D4 shapes. | Agent | D4 |
| 10 | My account | `account.html` | Account details, sign-in and security, appearance, and what the product may send. | Any signed-in user | D7, D6 |
| 11 | Auth | `auth.html` | Signing in, resetting a password, and an expired session, in both D6 shapes. | Stranger, any signed-in user | D6 |
| 12 | Notifications | `notifications.html` | The inbox, in both D8 shapes, with the unread count the chrome's badge reads. | Any signed-in user | D8 |

### Three screens folded in without a prototyping ticket

`auth.html`, `account.html` and `notifications.html` each have a build ticket
and no prototyping ticket. They are built here rather than left undrawn, and
they are listed in the handover so the tickets can be written against what was
actually decided. Each carries a decision of its own — D6, D7 and D8 — because a
screen that settles nothing does not get built, not even to fill a gap.

## Switches carried in the URL

Toggles, not states. They survive a move from screen to screen, they are written
into the address, and they multiply the screenshots rather than the markup —
exactly as S1 does it.

| 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` |
| `mobile` | `drawer` (default, and proposed), `bar` | all | **D5**: the navigation below 64rem. Above it the rail is S1's and does not change. |
| `form` | `steps` (default), `long` | `form.html` | **D1**: one step per registry group, or one page |
| `control` | `rich` (default), `plain` | `form.html`, `profile.html` | **D2**: whether the option count decides the control |
| `claim` | `inline` (default), `separate` | `index.html`, `claim.html` | **D3**: where the claim search is offered |
| `dash` | `clients` (default), `inbox` | `agent.html` | **D4**: what an agent's own area opens on |
| `session` | `inline` (default), `route` | `auth.html`, `profile.html` | **D6**: an expired session in place, or a route |
| `settings` | `sections` (default), `tabs` | `account.html` | **D7**: one page, or a tab strip |
| `inbox` | `grouped` (default), `flat` | `notifications.html` | **D8**: grouped by kind, or newest first |
| `role` | `club`, `pro` (default here), `agent`, `admin` | all | Who is signed in, and therefore which navigation entries exist at all. Not a decision: it is how S1's conditional entries are shown. **Not a role switcher in the product.** |

`role` defaults to `pro` in this prototype and to `club` in S1, because P2's
subject is a professional's own account. The navigation it produces is the same
navigation in both.

The variant bar sits under the prototype header on every screen, is hidden below
64rem so a phone screenshot shows the product rather than the paperwork, and
never appears in the product chrome.

## States per screen

Only the states a screen's content can actually reach are built. The vocabulary
is the repo's: `default`, `loading`, `empty`, `error`, `partial`,
`no-permission`.

| Screen | default | loading | empty | error | partial | no-permission | What the non-obvious ones are |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `index.html` | ✓ | ✓ | | ✓ | ✓ | | `error`: the email is already registered, which is the one sign-up error that must not become a way to learn who is registered; `partial`: the claim search found a probable match and the flow says so before anything is created |
| `verify.html` | ✓ | ✓ | | ✓ | ✓ | | `error`: a wrong or expired code; `partial`: the email is verified and the phone is not, which is the state most accounts actually sit in |
| `role.html` | ✓ | ✓ | | ✓ | | | `error`: the registry could not be read, so the form after this one cannot be drawn at all |
| `form.html` | ✓ | ✓ | | ✓ | ✓ | | `partial`: three of six groups saved and the rest outstanding; `error`: server validation disagreeing with the client, which is the case the registry makes likely. **There is no `no-permission` here**: the two values the person may read and not change — the club's contract and the platform's verification tier — are steady-state facts with an owner named on each, and S1 settled that such a field is not a refusal and never renders as one. They sit inside `default`. |
| `done.html` | ✓ | | | | ✓ | | `partial`: registered, but the phone is unverified and two registry groups are outstanding, so the profile is not in the market yet |
| `claim.html` | ✓ | ✓ | ✓ | ✓ | ✓ | | `empty`: the search matched nobody, which is the ordinary case; `partial`: a claim submitted and under review, with what was submitted still readable; `error`: a claim refused with the reason |
| `profile.html` | ✓ | ✓ | | ✓ | ✓ | ✓ | `error`: **the expired session**, which is where D6 is decided, over a half-edited form; `partial`: a media upload that failed after the rest saved; `no-permission`: a change to his own availability refused because his club has recorded a live injury |
| `mandate.html` | ✓ | ✓ | ✓ | ✓ | | ✓ | `empty`: no mandate either way; `no-permission`: an agent trying to confirm on the client's behalf, refused by name |
| `agent.html` | ✓ | ✓ | ✓ | ✓ | | | `empty`: an agent with no confirmed mandate |
| `account.html` | ✓ | ✓ | | ✓ | | ✓ | `error`: a password change refused because the current password is wrong; `no-permission`: two-factor enforced by the platform and therefore not switchable off |
| `auth.html` | ✓ | ✓ | | ✓ | ✓ | ✓ | `error`: wrong credentials, said once and the same way for a wrong password and an unknown email; `partial`: a password reset link sent; `no-permission`: **the expired session**, which is where D6 is decided |
| `notifications.html` | ✓ | ✓ | ✓ | ✓ | | | `empty`: nothing has happened to this account yet |

`no-permission` on `auth.html` is the expired session. It is filed there because
the person is signed out, not because the route is refused — and naming that
mismatch is part of what D6 asks. The state vocabulary has no `expired`, exactly
as it had no `refused` when S1 met the same problem.

### Which shell each screen is in

S1 settled that the product has two shells and not three. P2 is the prototype
where the boundary between them actually falls, so it is written down:

| Shell | Screens | What it carries |
| --- | --- | --- |
| **Public** | `index.html`, `verify.html`, `role.html`, `form.html`, `done.html`, `auth.html` | No rail, no bell, no account menu, a "sign in" button in its place. A person part-way through sign-up has an account and no standing in the product yet. |
| **Application** | `claim.html`, `profile.html`, `mandate.html`, `agent.html`, `account.html`, `notifications.html` | The full grouped navigation, the bell with its one count, the account menu. |

Two consequences the reviewer should weigh rather than inherit. The first: **the
mobile-navigation shots are taken on application-shell screens**, because the
public shell has no navigation to decide about — `profile.html` and
`notifications.html` carry them. The second: `claim.html` is in the application
shell even though the claim search is offered during sign-up, because a claim is
worked by a platform administrator over days and its owner has to be able to come
back to it. Under `claim=inline` the search itself appears **inside**
`index.html`, in the public shell, and only the claim's own screen is signed in.

## D5 — mobile navigation, in detail

The two shapes, and what each costs.

### a) A drawer from the header — `mobile=drawer` (proposed)

Below 64rem the rail is replaced by a single menu button in the header. It opens
a full-height panel carrying **the rail's list, unchanged**: the same groups,
the same group labels, the same entries this account holds, the same two badges, the same
absent-not-disabled rule. Nothing is privileged and nothing is dropped.

- Every navigation costs one extra tap.
- The screen below keeps its whole height, which is what a six-group form and a
  sticky primary action need.
- One list, one place, one rule. The 1280 rail and the 390 drawer are the same
  component with a different container.
- The badge count rides on the menu button, so the chrome still says how much
  work is owed without opening anything.

### b) A fixed bottom bar of four — `mobile=bar`

Four fixed destinations across the bottom — الرئيسية، الطلبات، الإشعارات،
المزيد — with "more" opening the same drawer.

- The two most-used destinations are one tap.
- It costs a permanent strip at the bottom of every screen, which is where a
  form's primary action wants to be, and the screenshots at 390 show the two
  colliding on `form.html`.
- Three of the four already carry a badge or are home, so the bar mostly
  restates what the chrome says.
- **The four are not the same four for every account.** A platform
  administrator has no الطلبات at all — S1 settled that — so the bar either
  differs per role, which is the role switcher S1 rejected arriving through the
  back door, or it shows an entry that account does not hold, which S1 also
  rejected.

Both are built, both are switchable, and the screenshots at 390 are taken on
`form.html` and `index.html` in both, because those two are where the cost is
visible rather than theoretical.

## The registry form, in detail

Three layers, which is what "three-layer dynamic form" means here, and the
prototype renders all three so the shape of the payload is arguable:

1. **The profile type** — لاعب، مدرب، حكم، وكيل، إداري. Chosen on `role.html`.
2. **The sport** — كرة القدم or كرة السلة, and only those two anywhere.
3. **The registry groups for that pair**, each holding attributes with a data
   type, a validation rule and — the open part of D2 — possibly an option count.

The six groups a player in football is asked for:

| Group | Attributes, with their data types |
| --- | --- |
| البيانات الأساسية | الاسم الكامل (نص)، تاريخ الميلاد (تاريخ)، الجنسية (قائمة مرجعية — hundreds)، الجنس (اختيار من اثنين) |
| البيانات الرياضية | المركز الأساسي (قائمة مرجعية — ثمانية)، المراكز الأخرى (اختيار متعدد)، القدم المفضّلة (اختيار من ثلاثة)، الرقم المفضّل (عدد) |
| القياسات | الطول (عدد بوحدة)، الوزن (عدد بوحدة) |
| الخبرة | الأندية السابقة (سطور متكررة)، سنوات الاحتراف (عدد) |
| الجاهزية | الحالة (اختيار من خمسة)، متاح من (تاريخ)، ملاحظة (نص طويل) |
| المستندات والوسائط | صورة (ملف)، مقاطع (روابط أو ملفات)، مستند إثبات (ملف) |

Two attributes exist on the screen and are **not** editable, with the reason
named on each: the contract and the club, which the club owns; and the
verification tier, which the platform owns. They render in the S1 pattern for a
value a person may read and not change — which is **not** the refusal pattern,
because nothing failed.

## Fixture world

This prototype uses the Stage 2 cast in [`../FIXTURES.md`](../FIXTURES.md) and
adds nothing to it. The people it leans on:

| | |
| --- | --- |
| Registering | **سلطان الرشيد** — حارس مرمى، لاعب حر، registered 10 سبتمبر 2026, born 4 مارس 1999 |
| Claiming | **ماجد الحربي** — a pre-created profile at اتحاد الوسام already held him; claimed 16 سبتمبر 2026 |
| Signed in on `profile.html` and `notifications.html` | **فيصل الدوسري** — نادي النهضة, contract to 31 ديسمبر 2026, three unread |
| The agent | **ريم المالكي** — three clients, one mandate confirmed, one renewal outstanding, one unanswered |
| The mandate awaiting an answer | ريم المالكي → سلطان الرشيد, sent 15 سبتمبر 2026 |
| The renewal awaiting an answer | ريم المالكي → فيصل الدوسري, to 31 يناير 2028, sent 17 سبتمبر 2026 |
| Today | 18 سبتمبر 2026 |

## Out of scope

Deliberately not answered here, so the review does not drift:

- The navigation entries, the landing, the club area, the shape of an approach,
  the refusal pattern and the absence of a role switcher. All six are S1's and
  locked; this prototype renders them.
- The market search and the profile the market opens. Both are P1's, locked, and
  linked rather than rebuilt.
- The club's own registration and its members. P4's.
- Sending an approach, the requests inbox and the published-needs board. P5's.
- The public club page and the module registry. P6's.
- Email as a rendered artefact. The inbox is in-app; what an email looks like is
  a template question and nothing in D8 turns on it.
- Payment, invoicing and anything with a price. Version one is free for
  everyone.

## Open questions

Surfaced by building this and not settleable by it alone. They are carried into
`findings.md` rather than answered by a fixture.

- If the registry spec must carry an option count for D2's answer to be
  buildable, that changes the onboarding endpoint's payload, and the endpoint is
  in Track A. The decision has to reach that epic before the endpoint is built,
  not after.
- A person may hold a professional profile, an agency licence and a club
  membership at once. Sign-up asks for one profile type. Nothing here says how
  the second one is added later, and S1's no-role-switcher answer means it has to
  be addition rather than switching.
- Verification tier is shown and not explained. What raises a tier, and who
  decides, is the claim queue's business and therefore P3's.
- An expired session during a **media upload** loses a file, not a form. Whether
  D6's answer covers that is not settled by either variant.

## Not built

Anything the timebox did not reach is listed here, so a half-built prototype
says which half is missing. Empty at the time of writing.
