# P6-public-directory-ads — screen inventory

| | |
| --- | --- |
| Title (ar) | الصفحات العامة ومساحات الإعلانات |
| Title (en) | The public pages and the ad spaces |
| Owning epic | D6.0 |
| Reviewer | Zayed |
| Timebox | 3 days |
| Opened | 2026-09-18 |
| Reviewed | — |
| Locked | — |
| Status | draft |

## The question

Everything else in Stage 2 is written for somebody who signed in. This prototype
is the one surface a stranger reaches: a search engine, a sponsor who has never
heard of the product, a parent looking for a club's under-17 team. It is also
the only surface where a mistake is permanent — a field published once is
published, and a search engine keeps it after the product stops serving it.

S1 already settled the **frame**: two shells, not three, and the public shell has
no rail. It also settled, in its secondary questions, exactly what a visitor
reads on a squad row. Neither is re-asked here; both are rendered.

What is left, and what P6 is for:

- The public page has to be **addressable in two languages**. Whether that is two
  URLs or one changes what is published, what is indexed and what a link shared
  in a WhatsApp group opens in. It is the only decision here that cannot be
  changed after launch without losing the addresses.
- A sponsor arrives with a number — 150,000 for the season — and needs the
  spaces she can afford. The **control that takes that number** decides whether
  the screen is a search or a browse.
- The **module registry** is the club owner's only view of what the club has and
  what it is missing, and one of its rows is a refusal that S1 locked the shape
  of. Cards or a table changes whether the dependency is legible at all.
- And where a signed-in reader meets a private field, **a visitor meets
  nothing**. What "nothing" looks like is unsettled and is the live remainder of
  the epic's fourth decision.
- Finally: **the review pattern P3 settled is reused twice here without
  changing it** — once by an administrator verifying a company or an external
  record, once by a club answering an incoming ad booking. If it does not
  transfer, P3's D3 is not the answer it claims to be.

## Decisions to settle

| Id | Decision | Options built | Switch |
| --- | --- | --- | --- |
| **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. | a) a locale segment in the path, two addresses, each canonical of itself — `url=locale-path` b) one address per club, the language negotiated and remembered — `url=one-url` | `url` |
| **D2** | **The budget control**, and how the results update. | a) a slider — `budget=slider` b) suggested amounts — `budget=bands` c) both, the amounts setting the slider — `budget=both` | `budget` |
| **D3** | **The module registry** — cards carrying their dependencies, or a table. | a) a card per module — `registry=cards` b) one table — `registry=table` | `registry` |
| **D4** | **What a visitor sees where a signed-in reader sees a private field.** Not what is private — that is locked — but what occupies the place. | a) nothing at all — `visitor=absent` b) the place kept, and an unclickable line saying members read more — `visitor=stated` c) the place kept, with a sign-in prompt — `visitor=prompt` | `visitor` |

### The epic's D4 was already answered, and this is what replaced it

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

**That question was answered on 17 September 2026 and locked on 18 September**,
in the S1 review's secondary questions, and it was answered with two clauses
added to it:

> A visitor's squad row is **name, number and position** — and it is also **no
> date of birth, no nationality, no photograph and no injury status**, so that
> 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** on `squad.html` and `club.html` and does not rebuild
it as a switch. A locked decision is superseded by a new prototype, never
re-asked inside one that inherits it.

What the locked answer leaves open, and what `D4` is here, is the **shape of the
absence**. The squad row is the clearest case but not the only one: the club
page has a contact, the roster has contract dates, the professional's page has a
salary band. A signed-in reader whose tier does not reach one of those meets
`.locked` — a steady-state fact that keeps the field's place and names the tier,
which S1 also locked. A **visitor holds no tier at all**, and it is not obvious
that the same answer applies to him: naming a field is itself a disclosure, and
"members see more" on nine rows is an advertisement wearing a data label. All
three answers are built on the same rows so the reviewer can read them against
each other rather than against a description.

### What the reviewer must answer

- **D1** — Both variants render the same club page and differ only in what the
  page publishes about itself: the address, the `canonical`, the `alternate`
  set, and the structured-data record, all four shown on the screen as literal
  text rather than described. A locale segment gives a search engine two
  documents to index in two languages and gives a person a link that opens in
  the language it was shared in; it also doubles the addresses and needs a rule
  for which one a bare `/clubs/al-sahra` resolves to. One address is a single
  thing to link and a single thing to rank, and it is silent about Arabic to
  anything that cannot run a negotiation. Answer: which, and — separately, and
  not switched because it should not differ between them — whether the
  **indexability table** on both variants is right: the club page and the
  edition page indexed, the squad list indexed, a pending club's address not
  served at all, and the sponsor's screens never indexed.
- **D2** — The sponsor's budget is 150,000 for the season, ten spaces exist
  across the cast and seven are at or under it. A slider is one gesture and
  makes the boundary feel continuous, at the cost of a query per release and of
  a number the sponsor cannot type. Suggested amounts are one tap, four
  defensible bands and an exact field for the rest, at the cost of pretending
  the market has four prices in it. Answer: which, and **when the results
  update** — the prototype updates on release and on choice, never on every
  pixel, and the copy says so under each control.
- **D3** — Seven modules, five running, one not activated, and one whose
  activation is **refused because another module it depends on is not
  activated**. Cards give the refusal a place to be a paragraph; a table gives
  the seven a shape you can read in one pass and squeezes the refusal into a
  cell. Answer: which, and whether the dependency belongs on the row at all or
  only on the refusal.
- **D4** — Above. Answer: which of the three, and whether the answer is allowed
  to differ between a squad row (where the absent fields are personal data about
  a third party) and the club's own contact details (where they are not).

## Screens

`p6.js` renders the chrome only. It is `p3.js` with this prototype's switches
and its own `HERE`; sections 0, 3 and 4 — the helpers, the navigation list and
the chrome — are the same code, because the entries, the grouping, the two
badges and the absent-not-disabled rule are S1's and not a prototype's to vary.
The one variation is `renderSubnav`, which renders the **club** tab strip and
not P3's administration one, because administration's internal shape is P3's D1
and not re-asked here.

**The public shell is `data-p-anon` on `<body>` and nothing else.** It is the
mechanism P2 already uses for sign-up and sign-in, arriving from the other side:
no rail, no bell, no account menu, and a sign-in button where the account menu
would be. There is no third shell and no second stylesheet.

| # | Screen | File | Shell | Purpose | Actor | Settles |
| --- | --- | --- | --- | --- | --- | --- |
| 1 | Club directory | `index.html` | **public** | The entry screen. Forty-six approved clubs, nine of them named and approved, searched by name and filtered by sport. The only list a stranger can reach. | Visitor | **D1** |
| 2 | A club's public page | `club.html` | **public** | نادي الصحراء as a stranger reads it: identity, the four published teams, the named head coach of each, the fixtures and results, and what a visitor does not get. The anonymous twin of the signed-in club page P4 owns. | Visitor | **D1**, **D4** |
| 3 | A published squad list | `squad.html` | **public** | The locked answer rendered: name, number, position, and the head coach named above the list. Nothing else. | Visitor | **D4** |
| 4 | An edition of a competition | `edition.html` | **public** | دوري المحترفين السعودي نسخة 2026/27 — the clubs entered, and the fixtures and results. No standing, and the screen says why. | Visitor | **D1** |
| 5 | Modules and plan | `modules.html` | application | The club account owner's only view of what the club has. Seven modules, and the dependency refusal S1 locked. | Club account owner | **D3** |
| 6 | The club's ad spaces | `spaces.html` | application | The club's own rate card — ad-space rates are the only prices in the product, and the product itself costs nothing — and the incoming booking it has to answer. **The review pattern, reused.** | Club member | D3 (refusal), the review pattern |
| 7 | The sponsor's budget search | `sponsor.html` | application | Search by budget, confirm a booking, and read the bookings already made. Confirmation only: nothing here invoices. | Sponsor's contact | **D2** |
| 8 | The verification queue | `verify.html` | application | Three companies and three external records awaiting a decision, in one queue and one shape. **The review pattern, reused a second time.** | Platform admin | the review pattern |
| 9 | One company under review | `company.html` | application | The evidence, the record it would change, and approve or refuse with a reason. The detail half, as a route. | Platform admin | the review pattern |

### How each screen is reached

Two of P6's screens are navigation entries and seven are not, so this is written
down rather than left to be discovered:

| Screen | Reached from |
| --- | --- |
| `index.html` | **the rail** — "دليل الأندية", which every account holds; and from outside the product entirely |
| `club.html` | a row in the directory |
| `squad.html` | a published team on the club page |
| `edition.html` | the fixtures section of the club page |
| `modules.html` | **the rail and the club tab strip** — "الوحدات والاشتراك", the account owner's alone |
| `spaces.html` | the modules screen, once مساحات الإعلانات is running |
| `sponsor.html` | **nothing in the navigation.** An advertiser account holds no entry at all, which is the finding below |
| `verify.html` | **nothing in the navigation.** Administration has five entries and six screens |
| `company.html` | a row in the verification queue, under `review=route` |

There is **no index of the prototype's screens** anywhere in the markup and none
was added: a screen that needs a directory of screens to be found is a screen
the product has not placed, and hiding that with a prototype-only link would
hide the finding with it.

### The signed-in club page and its anonymous twin

The S1 review carried this here by name: the club directory in the signed-in
navigation opened the anonymous visitor page, and whether that is **one route
rendered twice or two routes** was left to this prototype.

**P4 owns the signed-in club page.** P6 owns the anonymous one, and what it
finds is written into `findings.md` rather than switched, because it is a
carried review finding and not a decision of this prototype. What the two
screens differ in, read off the built pages:

| | The visitor's page (`club.html`) | The signed-in club's own page (P4's) |
| --- | --- | --- |
| Shell | public, no rail | application, rail and club tab strip |
| Who it is about | any approved club | the reader's own club, always |
| Squad | name, number, position | the roster, with contracts and the one injury |
| Staff | the head coach of each published team, named | all five, with their posts |
| Contact | **D4** decides what occupies the place | the club's own record, editable |
| Actions | none — there is nothing a stranger may do to a club | members, structure, roster, needs |
| Missing data | invisible | the three with no contract recorded, as a queue |

They are **not the same screen with a permission check**; they share a
subject and nothing else. That is the finding; the routing recommendation
that follows from it is in `findings.md`.

### The review pattern, reused twice and not changed

P3's five points are copied here verbatim and are the specification both
reuses are built against:

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.
   No count that is not the length of the queue.
2. A detail carries the evidence, the thing being decided about, and what the
   record would become — beside what it is now.
3. A decision is two buttons and a reason. Approve needs no reason. **Refuse
   requires one**, in free text, the person refused reads it verbatim, the field
   says so, and the refuse button is disabled until it is filled.
4. A decided item stays readable with its decision, its reason, its reviewer and
   its date.
5. The decision is recorded against a person, and that name stays in the "last
   edit by, and when" column.

`verify.html` and `company.html` are the administrator's reuse. `spaces.html`'s
incoming booking is the club's, and it is the harder test: the decider is not
platform staff, the queue is one row long, and the thing decided is commercial
rather than evidential. **Both are built from the five points without adding a
sixth.** Where a point did not fit, it is recorded in `findings.md` rather than
quietly dropped.

## 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` |
| `url` | `locale-path` (default), `one-url` | `index.html`, `club.html`, `squad.html`, `edition.html` | **D1**: the address, the canonical, the alternates and the structured data |
| `budget` | `slider` (default), `bands`, `both` | `sponsor.html` | **D2**: the control that takes the number, and when results update |
| `registry` | `cards` (default), `table` | `modules.html` | **D3**: a card per module, or one table |
| `visitor` | `absent` (default), `stated`, `prompt` | `club.html`, `squad.html` | **D4**: what occupies the place of a field a visitor does not reach |
| `review` | `split` (default), `route` | `spaces.html`, `verify.html`, `company.html` | **Inherited from P3's D3.** Not a decision here. It is carried so both reuses can be shot under whichever shape P3 settles. |
| `seat` | `owner` (default), `member` | `modules.html`, and the club's tab strip on every club screen | **Not a decision.** Which seat at نادي الصحراء is signed in — ناصر العمري, who owns the account, or **عمر الدخيل**, who does not. Every club fixture in Stage 2 held `owner: true`, so the owner-only rule — «الوحدات والاشتراك» is the account owner's alone, absent and never disabled for anyone else — was written and never once rendered. Now it renders. |
| `mobile` | `drawer` (default), `bar` | all | **Inherited from P2's D5.** Not a decision here. |
| `role` | `club` (default here), `pro`, `agent`, `admin`, `sponsor` | all | Who is signed in, and therefore which entries exist at all. `sponsor` is added by this prototype and holds **no navigation entry whatsoever**, which is a finding and not a defect. |

## States per screen

| Screen | default | loading | empty | error | partial | no-permission | What the non-obvious ones are |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `index.html` | ✓ | ✓ | ✓ | ✓ | ✓ | | `empty`: a filter that matches no approved club; `partial`: approved clubs that have **not activated a public profile**, listed by name with no page to open |
| `club.html` | ✓ | ✓ | ✓ | ✓ | ✓ | | `empty`: نادي صقور الرياض لكرة السلة, approved and publishing no team, so the page opens onto nothing; `partial`: فريق تحت 17 published with **no head coach assigned**, so the line the locked answer promises is absent for one team |
| `squad.html` | ✓ | ✓ | ✓ | ✓ | ✓ | | `empty`: كأس الاتحاد 2026/27 — the team is entered and **no squad list has been submitted**; `partial`: the under-17 list, published with no head coach above it |
| `edition.html` | ✓ | ✓ | ✓ | ✓ | ✓ | | `empty`: الدوري السعودي لكرة السلة 2026/27, which begins 10 أكتوبر 2026 and has no played match; `partial`: كأس الاتحاد 2026/27 — clubs entered, **no draw made**, so there is no fixture to show |
| `modules.html` | ✓ | ✓ | | ✓ | | ✓ | `error`: **the dependency refusal** — مساحات الإعلانات cannot be activated while الملف العام للنادي is not, and it is a refusal, not a failure: no retry, and it lifts the moment the dependency is on; `no-permission`: a club member who is not the account owner, opening the route |
| `spaces.html` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | `empty`: the module is on and no space has been priced yet; `partial`: the incoming booking **already answered**, read afterwards, with the refusal and its reason on the record; `no-permission`: the module is not activated for this club, so the route is refused in the shell and points at the registry |
| `sponsor.html` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | `empty`: a budget no space in the cast is under; `partial`: a result whose club has **no public page**, so the space is listed and the club's name does not open; `no-permission`: a club member who opened an advertiser's route |
| `verify.html` | ✓ | ✓ | ✓ | ✓ | | ✓ | the same three as P3's claims queue, because it is the same pattern |
| `company.html` | ✓ | ✓ | | ✓ | ✓ | | `partial`: **شركة بروق للدعاية**, refused on 14 سبتمبر 2026, read afterwards with its reason, its reviewer and its date; `error`: the decision could not be recorded and the reason typed is not lost |

## Fixture world

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

> **A correction to this section.** It used to say three sections were *added* to
> `FIXTURES.md` by this prototype. They were not: `git show 4b7add0 --
> FIXTURES.md` is empty, and the four companies, the verification queue, the ad
> market beyond نادي الصحراء's own three spaces, the fixtures and results, the
> shirt numbers and the club's four published teams existed only on these
> screens. They are declared in `FIXTURES.md` now, in a later pass, and marked
> **declared** below — the world stands, the claim that it had been merged does
> not.

| | |
| --- | --- |
| The club on every public screen | **نادي الصحراء** — معتمد، قسمان، أربعة فرق منشورة في 2026/27 |
| **Declared:** its sections and teams | two sections, **four teams in 2026/27 and three in 2025/26** — فريق تحت 17 was created 1 يوليو 2026 — and **two of the four with no head coach** |
| Its published teams | الفريق الأول لكرة القدم، فريق تحت 19، **فريق تحت 17 (لا مدير فني)**، فريق كرة السلة |
| Public staff | **بندر العقيل** (الفريق الأول لكرة القدم) و**طلال بن عيد** (فريق كرة السلة). The other three are not public and no staff contract is. |
| The empty club | **نادي صقور الرياض لكرة السلة** — approved, publishes no team |
| The edition | **دوري المحترفين السعودي نسخة 2026/27** — 16 clubs, squad list 18 of 25 |
| **Declared:** fixtures and results | four played, two to come, all between clubs already in the cast; كأس الاتحاد has no draw; basketball begins 10 أكتوبر 2026 |
| The sponsor | **شركة نسيج للرياضة** — لمياء الفهد — verified 13 سبتمبر 2026, budget 150,000 for 2026/27 |
| **Declared:** ad spaces | ten across the cast, **seven at or under 150,000**, three above it |
| Committed | 60,000 confirmed on نادي الصحراء's شريط إعلاني رقمي (16 سبتمبر 2026) and 90,000 requested on its لوحة جانبية (17 سبتمبر 2026) — **150,000 exactly, nothing remaining** |
| **Declared:** the verification queue — three companies, their contacts and the refused one | **three companies** (oldest 15 سبتمبر 2026) and **three external records** (oldest 15 سبتمبر 2026) — six items; **شركة بروق للدعاية** refused 14 سبتمبر 2026 and still readable |
| Today | 18 سبتمبر 2026 |

### The contradiction in the inherited cast, recorded and not resolved

The shared cast says two things that cannot both be true today, and **P6 renders
both rather than choosing**, because one of them is a decision S1 locked:

- "Modules and plan" says نادي الصحراء's **مساحات الإعلانات is not activated and
  its activation is refused**, because **الملف العام للنادي is not activated**
  either. That is S1's dependency refusal, locked, and `modules.html` renders it
  exactly.
- "Ads and sponsorship" says نادي الصحراء has **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.

So: `modules.html` renders the refusal; `spaces.html` renders the rate card and
the incoming booking as its `default`, and renders the refusal as its
`no-permission` state, which is the same club on the same day read the other
way. The directory's `partial` and the sponsor search's `partial` both render
the consequence — an approved club with no public page — so the contradiction is
on the screen rather than in a footnote. **Which of the two the cast means is a
question for the review, and a question it should answer:** whether an approved
club is in the public directory from the day it is approved, or from the day it
activates its public profile, is the difference between forty-six public pages
and an unknown smaller number.

## Out of scope

- **The frame.** Two shells, not three. S1's, locked, rendered here.
- **What a visitor reads on a squad row.** Answered by the S1 review with two
  clauses added, locked, rendered here, and replaced as `D4` by the question the
  locked answer leaves open. See above.
- **The review pattern's shape.** P3's `D3`. Carried as the `review` switch so
  both reuses can be shot under either answer, and not re-asked.
- **Administration's internal shape.** P3's `D1`. `p6.js` renders the club tab
  strip only.
- **The signed-in club page.** P4's.
- **Invoicing, payment, contracts for a booking.** A booking is confirmed here
  and nothing else happens to it. The epic parks invoicing.
- **A price on anything that is not an ad space.** Version one of every module is
  free for everyone and the product charges nobody for anything; `modules.html`
  says so once, in words, with no figure beside it. Ad-space rates are the only
  prices here, they are the club's own, and the same rate is read on the club's
  rate card and in an advertiser's budget search.
- **A standing table, a view count, a follower, a "most popular club".** A public
  directory is exactly where those creep in. There is no number on any public
  screen in this prototype that is not a date, a score, a shirt number or the
  length of a list.

## Open questions

- **Is an approved club public from the day it is approved, or from the day it
  activates its public profile?** The cast contradicts itself and both readings
  are rendered. See above.
- **A sponsor account holds no navigation entry at all.** S1's twelve entries
  were written before the sponsor existed; `role=sponsor` renders the rail S1
  gives an advertiser, which is nothing. Recorded, not repaired: S1 is locked.
- **Whether the verification queue is a sixth administration entry.** Stage 2's
  route map has five and all five prototypes render the same five. `verify.html`
  is reachable and highlighted by nothing, which is the evidence.
- **Whether a refused booking may be requested again**, and whether the club's
  refusal reason follows the space or the sponsor.
- **Whether an external record about a club that is still قيد المراجعة may be
  decided at all.** One of the three is exactly that, and the queue shows it
  waiting rather than hiding it.

## Not built

- **Tournament pages above the edition.** The epic names "tournament and edition
  pages"; only the **edition** is built, because every fact in the cast is
  attached to a نسخة and a tournament page with no edition-independent content
  would settle nothing. Recorded as a gap, not as a decision.
- **A standing.** Deliberately. The cast holds four results and a standing
  computed from four results is an invented number.
- **The sponsor's own company profile screen.** The sponsor is verified in the
  cast; the screen where a company registers and uploads its register is P2's
  shape, not this prototype's question.
