# P6-public-directory-ads — findings

| | |
| --- | --- |
| Reviewer | Zayed |
| Reviewed | — |
| Status | draft |
| Supersedes | — |
| Superseded by | — |

**This prototype is a draft and its outcome column is empty on purpose.** Every
decision below offers at least two defensible answers, all of them actually
built and all switchable from the bar under the prototype header. The choice
follows the reader from screen to screen and is written into the address.

## Outcome per decision

| Id | Decision | Options built | Outcome | Evidence | Writes back to |
| --- | --- | --- | --- | --- | --- |
| **D1** | **The public page's address and what it publishes.** One page per locale, or one page that negotiates? The answer decides the canonical, the alternates and the shape of the structured data — and it is the only decision here that cannot be changed after launch without losing the addresses. | a) a locale segment in the path — `url=locale-path` b) one address per club, the language negotiated — `url=one-url` | | `screenshots/directory--default--ar--locale-path.png`, `screenshots/directory--default--ar--one-url.png`, `screenshots/club--default--ar--locale-path.png`, `screenshots/club--default--ar--one-url.png`, `screenshots/edition--default--ar--locale-path.png`, `screenshots/directory--default--en--locale-path.png` | (a) the club-directory epic, and (c) a convention — **it changes what the public endpoints publish** |
| **D2** | **The budget control**, and when the results update. | a) a slider — `budget=slider` b) suggested amounts — `budget=bands` c) both, the amounts setting the slider — `budget=both` | | `screenshots/sponsor--default--ar--slider.png`, `screenshots/sponsor--default--ar--bands.png`, `screenshots/sponsor--default--ar--both.png`, `screenshots/sponsor--empty--ar.png`, `screenshots/sponsor--default--ar--mobile.png` | (a) the ad-space epic |
| **D3** | **The module registry** — a card per module carrying its dependencies, or one table. | a) cards — `registry=cards` b) a table — `registry=table` | | `screenshots/modules--default--ar--cards.png`, `screenshots/modules--default--ar--table.png`, `screenshots/modules--error--ar.png`, `screenshots/modules--error--ar--dark.png` | (a) the entitlements epic, and (b) a note on the dependency-check story |
| **D4** | **What occupies the place of a field a visitor does not reach.** Not what is private — that is locked — but what stands where it would be. | a) nothing at all — `visitor=absent` b) the place kept, stated, no invitation — `visitor=stated` c) the place kept, with a sign-in prompt — `visitor=prompt` | | `screenshots/club--default--ar--absent.png`, `screenshots/club--default--ar--stated.png`, `screenshots/club--default--ar--prompt.png`, `screenshots/squad--default--ar--absent.png`, `screenshots/squad--default--ar--stated.png`, `screenshots/squad--default--ar--prompt.png` | (b) a note on the anonymous club-and-squad-pages story, and (c) a convention |

### The epic's fourth decision was already answered, and what replaced it

The `[D6.0]` ticket's fourth decision reads "how much of the private market card
reappears on a public squad row (name, shirt, position only)".

**It was answered on 17 September 2026 in the S1 review's secondary questions and
locked on 18 September**, with two clauses added to it rather than a bare yes:

> A visitor's squad row is **name, number and position** — and also **no date of
> birth, no nationality, no photograph and no injury status**, so none of those
> is added later as "obviously public". And the staff question is answered
> rather than left open: **the head coach of each published team is public and is
> named, no other member of staff is**, and no staff contract is published.

P6 **renders that answer** and does not rebuild it as a switch, because a locked
decision is superseded by a new prototype and never re-asked inside one that
inherits it. `squad.html` is three columns and there is no fourth; the one injury
in the cast — عمر الشهراني's, recorded 12 سبتمبر 2026 — belongs to row eight of
that table and leaves no trace on it; `club.html` names بندر العقيل and طلال بن
عيد and no other member of staff.

**D4 above is what the locked answer leaves open:** a signed-in reader whose tier
does not reach a field meets `.locked`, which S1 also settled — it keeps the
field's place and names the tier, and it is a steady-state fact rather than a
refusal. A **visitor holds no tier at all**, and it is not obvious the same
answer carries: naming a withheld field is itself a small disclosure, and
"members see more" repeated down a page is an advertisement wearing a data
label. Under all three answers the withheld **value** is never published; what
differs is the sign above its absence. Evidence for the substitution itself:
`screenshots/squad--default--ar--absent.png` beside
`screenshots/squad--default--ar--stated.png`.

### Secondary questions the same screens answer

| Question | Where it is visible | Writes back to |
| --- | --- | --- |
| **The signed-in club page and its anonymous twin — one route rendered twice, or two routes?** Carried here by name from the S1 review. The two screens share a subject and nothing else: a different shell, a different subject rule, a different squad, a different staff list, and no actions at all on the public one. **Two routes** is what the built pages argue for; the reviewer decides. | `screenshots/club--default--ar--absent.png` against P4's signed-in club page | (a) the club-directory epic, and (c) a convention |
| **Is an approved club in the public directory from the day it is approved, or from the day it activates its public profile?** The difference is forty-six public pages against an unknown smaller number, and the shared cast contradicts itself about نادي الصحراء. Both readings are rendered. | `screenshots/directory--partial--ar.png`, `screenshots/sponsor--partial--ar.png`, `screenshots/modules--error--ar.png` | (a) the club-directory epic and the entitlements epic |
| **An advertiser account holds no navigation entry at all.** S1's twelve entries were written before the advertiser existed. Under the locked absent-not-disabled rule, a company account's rail is empty. Rendered, not repaired. | `screenshots/sponsor--default--ar--slider.png` at `role=sponsor` | (b) a note on the navigation story, and a candidate for a prototype that supersedes S1 |
| **Administration has five entries in Stage 2 and six screens.** The verification queue is reachable and highlighted by nothing. | `screenshots/verify--default--ar--split.png` | (b) a note on P3's D1 |
| **Is the budget a search aid or a ceiling that forbids?** The cast's sponsor has committed 150,000 of 150,000 and the product still offers her the booking button. | `screenshots/sponsor--default--ar--slider.png` | (a) the ad-space epic |
| **May a booking be requested again after a club refuses it, and does the refusal reason follow the space or the advertiser?** The same question P3 left open for refused clubs, now in two places. | `screenshots/spaces--partial--ar.png`, `screenshots/company--partial--ar.png` | (b) a note on the ad-space epic and on the club-registration epic |
| **May an external record about a club still under review be decided at all?** One of the six queue items is exactly that, shown waiting rather than hidden. | `screenshots/verify--default--ar--route.png` | (a) the external-records epic |
| **Is a squad list published as a page but never as person records?** Both address shapes publish `SportsTeam` and its coach and **no `Person` for any player**, so eighteen names are read and not harvested. | `screenshots/squad--default--ar--absent.png`, `screenshots/club--default--ar--absent.png` | (c) a rule for the code repository — **it changes the public endpoint's payload** |
| **Is a standing a number this product may show?** Not built, deliberately, and the edition page says why on the screen. | `screenshots/edition--default--ar--locale-path.png` | (b) a note on the fixtures epic |

### The review pattern, carried from P3 and reused twice

P3's five points were copied verbatim and both reuses were built against them
without adding a sixth. What was learned by moving them:

1. **The administrator's reuse — `verify.html` and `company.html`.** Six items of
   **two kinds** in one queue, companies and external records, decided with one
   shape. That two kinds fit one queue is the strongest evidence that P3 settled
   a pattern and not a screen: if each kind had needed its own, there was no
   pattern. Evidence: `screenshots/verify--default--ar--split.png`,
   `screenshots/verify--default--ar--route.png`,
   `screenshots/company--default--ar.png`, `screenshots/company--partial--ar.png`.
2. **The club's reuse — `spaces.html`.** The harder case, and it moved with one
   point's *meaning* changed rather than its shape: point two says "a detail
   carries the evidence", and here there is no document to examine — the
   evidence is the advertiser's identity and its verification date. The decider
   is not platform staff, and the queue is **one row long**, which is the
   pattern's worst case: a route costs a click out and a click back for a single
   decision, and side-by-side keeps a queue that is keeping nothing. Evidence:
   `screenshots/spaces--default--ar--split.png`,
   `screenshots/spaces--default--ar--route.png`,
   `screenshots/spaces--partial--ar.png`.
3. **The one point that did not need changing anywhere:** a refusal requires a
   reason, in free text, read verbatim by whoever is refused, with the field
   saying so and the button disabled until it is filled. It holds for a claim, a
   club, a company, an external record and a commercial booking alike.

---

# Write-back

Three destinations, one section each, filled in after the review.

**No requirement id from any parked requirement document may appear anywhere
below.** Restate it in the project's own words or drop it.

## (a) New stories — appended

Destination epics: the directory, the club page and the squad list to the
**public club and squad pages** epic; fixtures and the edition page to the
**fixtures and results** epic; the module registry to the **entitlements** epic;
the budget search, the rate card and the booking to the **ad spaces** epic;
company verification and external records to the **external records** epic.

**D1's answer changes an endpoint's payload and is written into its Track A epic
before the endpoint is built** — the canonical, the alternate set and the
structured-data record are the public endpoint's output, not a front-end
decoration.

```
- **As a <actor>**, I <do>, so that <outcome>.
  - Accept: <criteria>.
```

Two lines, the second indented by two spaces, append-only.

## (b) Notes on existing stories

Written after the review, keyed by 1-based position. Three are already known to
be needed:

| Epic | Which story | Note it will carry |
| --- | --- | --- |
| the anonymous club and squad pages | the story S1 already annotated | **D4's answer** — what occupies the place of what a visitor does not reach. The locked clause about name, number and position is not touched. |
| the module registry with its dependency check | the dependency-check story | **D3's answer**, and whether the dependency belongs on the row at all or only on the refusal |
| the navigation list | the story that holds the entry list | an advertiser's company account holds **no entry**, and the list has no group for one |

## (c) Rules and conventions for the code repository

| Glob | Title | Note |
| --- | --- | --- |
| — | public addresses and what they publish | **D1's answer**, in full: the path shape, the canonical, the alternate set and the structured-data record. Written once, because every public route obeys it. |
| — | what a public page never publishes | **no `Person` record for any player, in either address shape**, and no personal field beyond name, number and position on a squad row. This is a rule about the payload, not about the page. |
| — | the visitor's absence | **D4's answer**, and whether it may differ between personal data about a third party and the club's own contact details. |
| — | two shells, and which screens are in which | P6's nine screens named against S1's locked two-shell rule: four public, five in the application shell, and no third shell anywhere. |
| — | no number that is not a count of clickable work, on a public page too | the rule generalises from S1's landing to every public screen: a date, a score, a shirt number and the length of a list, and nothing else. |

---

## Rejected options

Filled in after the review.

| Option | Why it was rejected | Evidence |
| --- | --- | --- |

## What P6 found in the inherited decisions and in the shared cast

Recorded here rather than acted on, because a locked decision is superseded by a
new prototype and never edited.

1. **The epic's fourth decision was already answered by the S1 review**, with two
   clauses added. It is rendered here and replaced by the live question the
   locked answer leaves open. Written out in full above.
2. **The shared cast contradicts itself about نادي الصحراء's ad spaces.**
   "Modules and plan" says its **مساحات الإعلانات is not activated and its
   activation is refused** because **الملف العام للنادي is not activated** —
   which is S1's dependency refusal, locked. "Ads and sponsorship" gives the same
   club **three priced ad spaces**, a **confirmed booking of 16 سبتمبر 2026** and
   an **incoming booking of 17 سبتمبر 2026**, none of which a club with the
   module off can hold, and none of which a sponsor could have found, because a
   club with no public profile has no page for her to find it on. **Both are
   rendered rather than one being chosen:** `modules.html` renders the refusal
   exactly as locked; `spaces.html` renders the rate card and the incoming
   booking as its `default` and the same refusal as its `no-permission`; and the
   directory's and the search's `partial` states render the consequence — an
   approved club with no public page. The reviewer decides which the cast means,
   and the answer is the same answer as "is an approved club public from the day
   it is approved?".
3. **An advertiser has no place in S1's navigation.** The `[D6.0]` epic requires
   a sponsor journey; S1's locked twelve entries hold nothing an advertiser's
   company account can have, and under the locked absent-not-disabled rule that
   means an empty rail. `role=sponsor` renders it. This is the one place where
   the epic and the inherited decisions are in plain conflict, and it is
   recorded, not repaired.
4. **Administration will have six screens and has five entries.** Not a
   contradiction — P3's D1 is exactly the question of what administration's
   inside looks like as it grows — but P6 is the prototype that pushes it past
   five, and `verify.html` is reachable with nothing in the rail highlighted.

## Not settled

- **Whether an approved club is in the public directory before it activates its
  public profile.** Above, and it is the question that decides how many public
  pages this product has.
- **Whether a refused booking may be requested again**, and whether the club's
  refusal reason follows the space or the advertiser. The same shape as P3's
  open question about a refused club re-registering.
- **Whether an external record about a club still under review may be decided.**
  One of the six is exactly that and the queue shows it waiting.
- **What a bare address resolves to under `url=locale-path`.** The panel renders
  `GET /clubs → 302 /ar/clubs`, which presumes Arabic wins for everyone. Whether
  a first-time English reader should be redirected to Arabic and then have to
  switch is not settled by this prototype.
- **The city facet of the directory.** Not one club in the cast has a city
  recorded, and inventing nine cities for nine clubs makes a directory that looks
  right and is not. Deferred, not rejected.
- **Whether the club needs a list of the advertisers it deals with.** It sees one
  incoming booking and its own rate card, and nothing else.

## Not built

- **A tournament page above the edition.** The epic names "tournament and edition
  pages"; only the edition is built, because every fact in the cast hangs off a
  نسخة and a tournament page with no edition-independent content settles nothing.
  A gap, not a decision.
- **A standing.** Four results are known in the cast; a standing computed from
  four results is an invented number and exactly what the no-vanity-numbers rule
  forbids. The edition page says so on the screen.
- **The company's own registration and document-upload screen.** The cast's
  sponsor is already verified; the screen where a company registers and uploads
  its commercial register is P2's shape, not this prototype's question.
- **Anything that invoices, prices a module, or takes a payment.** Version one of
  every module is free for everyone and the product charges nobody for anything.
  The only prices in the prototype are **ad-space rates**: a club sets the rate
  for its own space, and the same rate is read on that club's rate card and in an
  advertiser's budget search — one number in two views, not two numbers, which is
  why the budget search showing other clubs' rates does not break the rule.

## What was added to the shared files

> **This section used to be false, and is corrected rather than deleted.** It
> claimed three new sections in `../FIXTURES.md`. The commit that shipped this
> prototype does not touch that file — `git show 4b7add0 -- FIXTURES.md` returns
> nothing — so the world below existed only on these screens while five
> prototypes were reviewed against a cast that did not hold it. It is **declared
> now**, in a later pass, and the claim is rewritten to say what happened.

- **`../FIXTURES.md`** — **declared in a later pass, not in this prototype's own
  commit**: **fixtures and results** (four played, two to come, all between clubs
  already in the cast; كأس الاتحاد with no draw; basketball beginning 10 أكتوبر
  2026; and the explicit rule that no standing is ever drawn); **shirt numbers**
  for the five named first-team players and راكان العتيبي, because a public squad
  row is name, number and position and the number had to exist; **ad spaces
  across the cast** — ten in all, seven at or under 150,000 — **companies and
  external records**, three of each awaiting review plus شركة نسيج للرياضة
  verified on 13 سبتمبر 2026 and شركة بروق للدعاية refused on 14 سبتمبر 2026,
  every contact named; **نادي الصحراء's sections and teams**, which is where the
  «أربعة فرق منشورة» this prototype prints comes from — the cast's تحت 17 and تحت
  19 were age-category catalogue values and not teams — and لمياء الفهد's unread
  count of **0**. The price rule is restated there too, because this prototype
  broke it by reading it too narrowly: see "Out of scope" above.
- **`../shared/base.css`** — two new class names and nothing else. `.code`, a
  literal block for an address, a response header or a structured-data record —
  text read as text, never reflowed, always `dir="ltr"` on the element. `.range`,
  the browser's own range input given the product's accent and the full measure.
  **No media query was added and no existing rule was touched**, so every locked
  prototype renders exactly as before.
