# P5-requests-offers — screen inventory

| | |
| --- | --- |
| Title (ar) | الطلبات والعروض |
| Title (en) | Requests and offers |
| Owning epic | D5.0 |
| Reviewer | Zayed |
| Timebox | 3 days |
| Opened | 2026-09-18 |
| Reviewed | — |
| Locked | — |
| Status | draft |

## The question

This is the part of the product where two parties disagree in writing and a
record has to survive the disagreement. Everything else in the platform is one
person editing one thing; here a club, a professional, his agent and a fourth
party who is not in the conversation at all — the club that holds his contract —
each take turns on the same object, and the object has to read correctly to all
four at every moment.

S1 already settled the **shape**: an approach is one record with the terms in the
list and a terms record with its history beneath on the detail, the message
riding on the history row, six server-enforced states, and one shape for both
sides differing only in the filter each opens on. That is locked on 18 September
2026 and this prototype renders it rather than re-asking it.

What is left, and what P5 is for, is everything the locked shape does **not**
say:

- It says the terms are in the list. It does not say **which terms** — and the
  terms of a permanent transfer, a loan and a staff contract are not the same
  fields, and A1's are two lines long.
- It says a counter is a transition. It does not say **how a counter is
  authored**, and that is the difference between one terms object with a history
  and two terms objects one of which wins.
- It says the owning club's consent exists. The S1 review found the screen that
  proves it does not: `offer.html?state=partial` showed the consent outstanding
  for three days, to the owning club, **with no consent control anywhere**. S1
  wrote two stories for it and this prototype has to draw them.
- It says nothing at all about the deal type, and a player, a coach and a referee
  are not the same deal.

And the part the S1 review named separately: **the pull side of the marketplace
has no front door**. "Today's needs" rendered the club's own needs *manager* to a
professional. The board is where a professional and an agent read what clubs are
looking for and answer it, and until it exists the marketplace only pushes.

## The epic's D1 was already answered, and what replaces it

The `[D5.0]` ticket asks, as its first decision, "the thread shape: chat-like
timeline vs a structured terms table with history". **That is S4, and S1 locked
it on 18 September 2026**: the record wins, the message rides on the history row
so the history *is* the thread, the two sides differ only in their default
filter, and the board by state was rejected as the primary list and is welcome
only as a view toggle on the club's list. Rebuilding that as a switch here would
ask the reviewer to re-decide something with a screenshot already behind it.

So this prototype **renders the locked answer on every screen that shows an
approach**, and replaces the epic's D1 with the live question the locked answer
leaves open:

> **The locked answer says the terms are in the list. It does not say which
> terms.**

That is `D1` below. The other candidate — where the club's rejected
board-as-a-view-toggle belongs — is left alone on purpose: S1 already answered
it with a placement ("welcome later as a view toggle on the club's list") and
its own evidence for rejecting it as the primary list is a screenshot of six
columns wrapping at 1280. It is recorded in `findings.md` as inherited, not
rebuilt.

## Decisions to settle

| Id | Decision | Options built | Switch |
| --- | --- | --- | --- |
| **D1** | **What the record's list row carries when the terms are long.** S1 put the terms in the list and did not say which. A1's standing terms are «عقد سنتان من 1 يناير 2027، وبدل انتقال 2,400,000 ريال، مع بند أداء»; a loan's terms are a duration, a wage split and a buy option; a staff contract has no transfer fee at all. | a) **a headline** — the deal type and the one term that changed last, in one sentence, with the state and whose move — `row=headline` b) **fixed columns** — the same column set for every deal type, blank where a type has no such term — `row=columns` | `row` |
| **D2** | **How a counter is authored.** | a) **edit the terms in place** — the standing terms become editable, every changed field is marked, and the history row records what moved — `counter=inplace` b) **a new terms card beside the old** — the standing terms stay read-only and the counter is composed next to them, both visible until one is accepted — `counter=beside` | `counter` |
| **D3** | **Where the owning club's consent appears, for each of the four parties.** The S1 review's gap. Both answers keep it a first-class action with its own refusal reason, and both put it in the owning club's work queue; they differ in where the *control* lives and in what the other three parties read. | a) **on the approach** — a third action block on the record itself; the owning club sees the buttons there, the other parties read the same block as a state line — `consent=onrecord` b) **a step of its own** — the approach carries a read-only consent line for everyone, and the decision is taken on `consent.html`, which the owning club reaches from its queue — `consent=step` | `consent` |
| **D4** | **How the deal-type list and the loan fields adapt per target role.** | a) **computed from the target** — the list only ever offers what that role can be, the loan block exists only where the role can be loaned, and a role with no deal type gets a stated refusal instead of an empty select — `dealtype=perrole` b) **one common list** — the whole vocabulary is always offered and what does not apply to this target is shown disabled with the reason on it; the refusal happens on send — `dealtype=common` | `dealtype` |

### What the reviewer must answer

- **D1** — The headline reads as a sentence and survives any deal type, and it
  cannot be scanned down a column: two rows of five are not comparable. The
  columns can be scanned and they are half empty the moment a loan and a staff
  contract sit in the same list, because a staff contract has no transfer fee and
  a loan has no permanent fee. Answer: which, **and whether the row shows the
  terms as they stand now or the terms as the reader last left them** — a club
  that countered on 8 September and reads the row today wants to know the row
  changed, not only what it says.
- **D2** — In place is one terms object with a history: there is exactly one set
  of standing terms at every moment, which is what the consent step and the deal
  record both need, and the cost is that the thing you are arguing against
  disappears as you type over it. Beside is two objects and a winner: you read
  the two side by side, which is how a person actually negotiates, and the cost
  is that "the terms on the table" stops being a single value and every screen
  downstream — the row, the consent step, the deal — has to ask which one.
  Answer: which, and **if it is beside, what the list row and the consent step
  read while two cards are open.**
- **D3** — This is the gap. Consent is the only transition taken by a party who
  is not in the conversation: نادي الصحراء is not negotiating with نادي نسور تبوك,
  it is being asked to release a player whose contract it holds. On the record,
  the club meets the consent where the terms are, which is where the information
  it needs to decide already is — and every other party sees a control they can
  never use. As a step, the other three parties read a clean line and the owning
  club gets a screen that is only about its own decision — and the club has to
  leave the terms to take it. Answer: which, and **whether a refused consent
  closes the approach or returns it to the two parties to re-agree.**
- **D4** — «إعارة» on a coach is nonsense, and a referee is not contracted by a
  club at all. Computed from the target means the send form is different per
  role, which is one more thing the registry has to say; the common list means
  one form and a refusal after the fact, which contradicts the spirit of
  "eligibility is checked before sending" without contradicting its letter.
  Answer: which, and — separately — **what a club records with a referee, if
  anything**, because under either answer the list for عوض الزهراني is empty and
  the prototype refuses to invent a type.

## Not up for debate, carried from the epic and rendered as given

- **Eligibility is checked before sending.** The refusal is on the send screen,
  before the form, and the form behind it is inert.
- **Recipients are resolved once, at send time.** The send screen shows the
  resolution as a block the sender reads before sending, and the approach records
  it as the second row of its history.
- **No commission anywhere.** The platform is not a party and takes nothing. The
  line is on the approach, on the deal and on the need.
- **The states are the six the server enforces** — `مرسل`، `قيد المراجعة`،
  `عرض مقابل`، `مقبول`، `مرفوض`، `مسحوب` — and no club configures them.

## Screens

`p5.js` renders the chrome only, and is `p3.js` with this prototype's switches
and its own `HERE`. The navigation list, the grouping, the two badges and the
absent-not-disabled rule are identical in both files, because they are S1's and
not this prototype's to vary. The sub-navigation is the **club** tab strip, as
`p2.js` renders it; there is no administration area here.

| # | Screen | File | Purpose | Actor | Settles |
| --- | --- | --- | --- | --- | --- |
| 1 | Send an approach | `index.html` | The composition, from a profile: the target, the deal type, the terms, the loan block, the resolved recipients, and the eligibility refusal in front of it. The entry screen. | Club member | **D4** |
| 2 | The approaches list | `inbox.html` | One list, three readings. The club opens on "waiting on them", the professional and the agent on "waiting on you" — S1's answer, rendered rather than re-asked. The row is D1. | Club, professional, agent | **D1**, D3 |
| 3 | One approach | `offer.html` | The terms record with its history beneath, the message on the history row, every transition it can take, and the counter composer. | All three parties | **D2**, **D3** |
| 4 | The owning club's consent | `consent.html` | D3 (b) as a real route: the club that holds the contract, the player it is being asked to release, and consent or refusal with a reason. | The owning club | **D3** |
| 5 | Deals | `deals.html` | What an approach becomes when the last transition is taken, and the one deal that is not there yet because a consent is outstanding. | Club member | closure |
| 6 | Friendly proposals | `friendly.html` | A club-to-club proposal with no professional in it, no eligibility window and no owning club — the same record shape stretched to something that is not a transfer. | Club member | — (a secondary question) |
| 7 | The published-needs board | `board.html` | **The missing front door.** What approved clubs are looking for, read and answered by a professional and by an agent on a client's behalf. | Professional, agent, club, admin | the review finding |
| 8 | The club's needs | `needs.html` | The club's own manager: publish, close, read the applications. A professional who reaches this route is refused **onto the board**. | Club member | the role boundary |
| 9 | One need and its applications | `need.html` | The striker need, its twelve applications, four of them unread, and نايف الشهري at 29 against a published term of «حتى 26». | Club member | the two open questions |

### Why the board, the needs manager and one need are in this prototype

The S1 review recorded that "today's needs" rendered the club's own needs
manager to a professional: the pull side of the marketplace had a back office and
no front door. The shared navigation already points at
`../P5-requests-offers/board.html`, `needs.html` and `need.html`, so those three
hrefs are this prototype's to honour or to leave broken. They are honoured, and
the boundary between them is drawn on the screen: **`needs.html` refuses a
professional and sends him to `board.html`, and the professional's empty
approaches inbox lands on the board too.** An empty state that offers a club
action to someone who is not a club is the defect S1 caught, and it is not
repeated here.

## 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` |
| `row` | `headline` (default), `columns` | `inbox.html`, `deals.html`, `friendly.html` | **D1**: what the list row carries |
| `counter` | `inplace` (default), `beside` | `offer.html` | **D2**: how a counter is authored |
| `consent` | `onrecord` (default), `step` | `inbox.html`, `offer.html`, `consent.html` | **D3**: where the owning club's consent appears |
| `dealtype` | `perrole` (default), `common` | `index.html` | **D4**: how the deal-type list and the loan fields adapt |
| `mobile` | `drawer` (default), `bar` | all | **Inherited from P2's D5.** Not a decision here; carried so P5's screens can be shot at 390 under whatever P2 settles. |
| `role` | `club` (default here), `pro`, `agent`, `admin` | all | Who is signed in, and therefore which rows exist and which filter the list opens on. Not a product feature: S1 settled that there is no role switcher. |
| `drawer` | `open` | all | Opens the mobile drawer so a shot of it is reproducible from the address. |

## States per screen

| Screen | default | loading | empty | error | partial | no-permission | What the non-obvious ones are |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `index.html` | ✓ | ✓ | | ✓ | ✓ | ✓ | `partial`: the target is سلطان الرشيد, whose agent's mandate he has not confirmed, so the recipient resolves to him alone and the screen says which recipient it could not resolve; `error`: the send failed and the composed terms are still on the screen; `no-permission`: **the eligibility refusal** — عبدالله القحطاني, window 1 يناير 2028 |
| `inbox.html` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | `empty`: **role-aware** — a club with no approach lands on the market, a professional and an agent land on the published-needs board; `partial`: the الطلبات badge counts five things and this list holds the two that are approaches, and the screen says where the other three are; `no-permission`: a platform administrator, who is party to no approach |
| `offer.html` | ✓ | ✓ | | ✓ | ✓ | ✓ | `default`: A1 — عرض مقابل, نادي الصحراء's move; `partial`: **A3 — مقبول, awaiting the owning club's consent**, which is where D3 renders; `error`: the transition could not be recorded and the typed reply is not lost; `no-permission`: an account that is not a party to this approach |
| `consent.html` | ✓ | ✓ | | ✓ | ✓ | ✓ | `partial`: a consent already recorded, read afterwards, with its date and the name of who recorded it; `no-permission`: a club that is not the owning club on this approach |
| `deals.html` | ✓ | ✓ | ✓ | ✓ | ✓ | | `partial`: a deal that cannot close because the owning club's consent is outstanding — the row exists and its end date is blank; `empty`: a club that has closed nothing |
| `friendly.html` | ✓ | ✓ | ✓ | ✓ | | ✓ | `no-permission`: a professional or an agent — a friendly is between two clubs |
| `board.html` | ✓ | ✓ | ✓ | ✓ | ✓ | | `partial`: **a need published on a position that was deactivated in the catalogue on 17 سبتمبر 2026** — the need is still open and the board says the position is no longer in the catalogue; `empty`: no open need matches the filters |
| `needs.html` | ✓ | ✓ | ✓ | ✓ | | ✓ | `no-permission`: **the refused route that lands on the board** — the S1 review's finding, drawn |
| `need.html` | ✓ | ✓ | ✓ | ✓ | | ✓ | `empty`: the centre-back need, published and answered by nobody; `no-permission`: a professional who reached a club's need detail |

**All six state names render somewhere in this prototype, and all six approach
states render across its screens** — `مرسل` (A2, the club's list), `قيد المراجعة`
(A6, the agent's list), `عرض مقابل` (A1, everywhere), `مقبول` (A3,
`offer.html?state=partial`), `مرفوض` (A4, the club's list), `مسحوب` (A5 and A7,
both lists). **No single list claims to render all six**, because no party is on
all seven approaches, and each list says on the screen how many of the six it
holds and where the rest are.

## Fixture world

The Stage 2 cast in [`../FIXTURES.md`](../FIXTURES.md).

| | |
| --- | --- |
| Today | **18 سبتمبر 2026** |
| Signed in | **ناصر العمري** (club), **فيصل الدوسري** (pro), **ريم المالكي** (agent), **هند الشمري** (admin) |
| The approach worked in full | **A1 — نادي الصحراء → فيصل الدوسري**, routed to ريم المالكي, sent 8 سبتمبر 2026, countered by her on 12 سبتمبر 2026, and نادي الصحراء's move ever since |
| The approach awaiting consent | **A3 — نادي نسور تبوك → تركي الحربي**, both parties agreed 15 سبتمبر 2026, **نادي الصحراء's consent outstanding as the owning club** |
| The club's list | A1–A5, five of the six states |
| The professional's and the agent's list | A1, A6, A7, three of the six states |
| The eligibility refusal | **عبدالله القحطاني** and nobody else — contract to 30 يونيو 2028, window opens 1 يناير 2028 |
| The needs board | **six open needs**; نادي الصحراء publishes three of them |
| The need worked in full | **رأس حربة — كرة القدم**, حتى 26 سنة، عقد ثلاث سنوات, closes 31 يناير 2027, **twelve applications, four unread** |
| The applicant who is the evidence | **نايف الشهري**, 29, against a published term of «حتى 26» |

### Four people and three records added to the cast for this prototype

Added to `FIXTURES.md` in its own style, contradicting nothing already there,
because P5 needs a target the existing cast cannot supply:

| Added | Why it had to be added |
| --- | --- |
| **عماد الغانم** — جناح أيمن — اتحاد الوسام — عقد حتى 31 ديسمبر 2026 | The loan form needs a **contracted** target whose negotiation window is **open**, and the only such person in the cast was فيصل الدوسري, who already has three approaches on him and may not have a fourth. |
| **طارق بن حمدي** — مدرب حراس المرمى — نادي محاربي نجد — عقد حتى 31 ديسمبر 2026 | D4 needs a coach who can actually be approached today. عبدالله القحطاني is the eligibility refusal and may not be anything else. |
| **عوض الزهراني** — حكم — القائمة الوطنية — لا نادٍ | D4 needs a referee, and the cast had none. His deal-type list is empty under both answers, which is the finding, not an omission. |
| **The one closed deal** — نادي الصحراء ← بندر العقيل، عقد عمل، أُغلق 14 أغسطس 2026 | `deals.html` needs a deal that actually closed, and the cast's only deal in motion — A3 — is the one that has **not** closed. He was a free coach at the time, so no owning club's consent was involved, which is what makes him a clean closure. |
| **Three friendly proposals**, one of them awaiting نادي الصحراء | `friendly.html` needs an answer side. The incoming one is recorded in `FIXTURES.md` as **deliberately not one of the club's five**, and whether an unanswered friendly is work owed is an open question below rather than a sixth row nobody counted. **The screen drew four for a while, three of them wrong; it now draws F1, F2 and F3 and nothing else**, and `FIXTURES.md` carries each one's proposal date, answer, match date and ground so a fourth cannot be invented again. |

## Out of scope

- **The shape of an approach.** S1's S4, locked. Rendered, not re-asked.
- **The refusal pattern.** S1's S6, locked. Every refusal here names what was
  refused, the rule, the date it changes where one exists, and the one thing to
  do next — in place, except the two refused routes, where the content region is
  replaced inside the shell.
- **Mobile navigation.** P2's D5, carried.
- **The public twin of the board.** The board renders inside the signed-in shell
  with "this page is public" stated on it, exactly as S1's visitor page does.
  Which route serves the stranger belongs to P6.
- **Publishing a need.** The form that creates one is the club-admin prototype's;
  `needs.html` manages needs that exist.
- **The watchlist and the market.** P1's, locked.
- **Anything with a price.** Version one is free for everyone, and the only
  numbers on these screens are the terms of a deal between two parties and
  counts of rows.
- **Commission.** There is none, anywhere, and the screens say so rather than
  leaving a silence for somebody to fill.

## Open questions

Surfaced here and not answerable by a fixture. The first two are the reviewer's
and a fixture that presumed either answer would destroy the evidence.

1. **May a professional apply to a need published by the club he is contracted
   to?** No applicant in the cast is contracted to the club whose need he
   applies to, and none was invented. The question is on `board.html` as a
   question.
2. **Is an application validated against the need's published criteria, or is
   filtering entirely the club's problem?** نايف الشهري is 29 against a published
   term of «حتى 26» and his application is the first row. `need.html` states the
   term, states his age, and does nothing about it, on purpose.
3. **What does a club record with a referee?** Under both answers to D4 the
   deal-type list for عوض الزهراني is empty. The prototype refuses to invent a
   type.
4. **Is an unanswered friendly proposal work owed?** It is not one of the club's
   five, and the الطلبات badge does not count it. Whether it should is open.
5. **Does the الطلبات badge count approaches, or work?** It reads five for the
   club, of which two are approaches; the other three are applications, expiring
   contracts and an unanswered invitation. `inbox.html?state=partial` is that
   sentence drawn.
6. **Does a closed deal leave the approaches list?** `deals.html` is a list of
   its own and the approach stays readable in `inbox.html` under a closed filter.
   Whether that is one record in two lists or two records is not settled.
7. **Does a friendly proposal use the approach record at all?** It is drawn with
   the same record and history here because the shape stretched without
   complaint, and there is no eligibility window, no owning club and no
   professional in it.

## Not built

- **The counter-offer's own eligibility recheck.** If a counter moves the
  contract start date past the window, nothing here rechecks it.
- **Withdrawing an application** to a published need, from the professional's
  side. The board shows what he has answered and its state, and no way back out.
- **The second sport.** Every approach in the cast is football. The basketball
  needs are on the board and none of them is worked through to an approach.
