Commit 2ef6e088 authored by Mahmoud Aglan's avatar Mahmoud Aglan

docs(mobile-portal): the approved programme, its adversarial review, and what it supersedes

Another session builds the portal from here, so the entry point has to survive
being read cold.

docs/specs/mobile-portal/ holds three files. 01-program-plan.md is the approved
programme — decisions, workstreams, art direction, the full feature inventory and
verification. 02-critique-addendum.md is a four-lens review of that plan
(completeness, security/abuse, financial integrity, delivery risk) with every
claim checked against the code; where the two disagree the addendum wins, and it
replaces the plan's build order with S0–S10. 00-README.md is the map.

The README leads with four premises the plan was written on that turned out to be
false, because each changes what gets built: Livewire is ^4.3 not 3 (so a public
property is client-settable and validating in mount() is not enough); `dark:`
compiles to prefers-color-scheme with no @custom-variant declared, so ~900
utilities are live and untested rather than inert; `transactions` is one row with
debit and credit account columns, not a pair, contradicting CLAUDE.md and two
agent-rules files; and most of the domain the portal needs already exists.

That last one is the real hazard on this programme. The block-builder engine, the
parent portal, the push stack and the pricing entry points are all built, so the
README lists them explicitly under "do not rebuild" — the plan originally proposed
a second CMS before the review found the first one is generic enough to reuse.

Banners on mobile-app-plan.md, mobile-api-implementation.md and openapi.yaml:
all three describe the /api/v1 surface deleted in the previous commit, and a
native-Flutter-per-client approach that was replaced. Left in place as history,
marked so nobody builds from them.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
parent 883391c7
# =====================================================================
# SUPERSEDED — DO NOT BUILD FROM THIS SPEC.
#
# This documents the /api/v1 surface, which has been DELETED from the
# codebase. It carried an authentication bypass, an unauthenticated
# academy-wide push broadcast, and several endpoints with missing or
# inverted ownership checks.
#
# Current programme: docs/specs/mobile-portal/00-README.md
# Kept for history only.
# =====================================================================
openapi: 3.1.0 openapi: 3.1.0
info: info:
title: El Captain Sports Management — Mobile API title: El Captain Sports Management — Mobile API
......
> **⚠️ SUPERSEDED — DO NOT BUILD FROM THIS DOCUMENT.**
>
> It plans a native Flutter app per client against a REST API at `/api/v1`.
> **That API was deleted** (it carried an authentication bypass), and the approach
> changed to a member-facing web portal with a thin WebView shell.
>
> Current programme: [`docs/specs/mobile-portal/`](mobile-portal/00-README.md).
> Kept for history only.
---
# Mobile API Implementation Plan — System Side # Mobile API Implementation Plan — System Side
This is the step-by-step work to prepare the Laravel backend for the Flutter mobile app. This is the step-by-step work to prepare the Laravel backend for the Flutter mobile app.
......
> **⚠️ SUPERSEDED — DO NOT BUILD FROM THIS DOCUMENT.**
>
> It plans a native Flutter app per client against a REST API at `/api/v1`.
> **That API was deleted** (it carried an authentication bypass), and the approach
> changed to a member-facing web portal with a thin WebView shell.
>
> Current programme: [`docs/specs/mobile-portal/`](mobile-portal/00-README.md).
> Kept for history only.
---
# Mobile App Plan — Client App (Guardians & Participants) # Mobile App Plan — Client App (Guardians & Participants)
## Vision ## Vision
......
# Mobile Portal — programme documents
Everything needed to build the member-facing mobile web portal (players + guardians),
served from each client's own installation, with a thin per-client Flutter WebView shell
on top. **Read this file first.**
Paths in these documents are relative to the **repository root**, not to this folder.
---
## Read in this order
| # | File | What it is |
|---|---|---|
| 1 | `01-program-plan.md` | The approved programme: context, decisions, workstreams W0–W10, art direction, full feature inventory, build order, verification. |
| 2 | `02-critique-addendum.md` | A four-lens adversarial review of that plan (completeness, security/abuse, financial integrity, delivery risk), every claim verified against the code. **Where the two disagree, the addendum wins.** |
The addendum is not commentary. It corrects the plan, adds P0 work the plan missed, cuts
work the plan included, and replaces its build order with the S0–S10 sequence. Do not start
from the plan alone.
---
## Status
| Stage | State |
|---|---|
| **S0 — production safety** | **Done and pushed.** Vulnerable `/api/v1` surface deleted, 500-page disclosure closed, `ParentHome` IDOR locked, excuse-form PII write removed, entrypoint fails on migration error, env whitelist fixed, nginx exact-match locations added, page-builder fallback no longer answers reserved prefixes. |
| S1–S10 | **Not started.** This is the whole project. |
One item from S0 is deliberately **not** shipped: a migration revoking the old `mobile`
Sanctum tokens. It changes live client data, which the CLAUDE.md push rule says to ask
about first. It is not required for safety — the routes it protected no longer exist.
---
## Four premises that are false — do not inherit them
These were wrong in the original plan and are corrected in the addendum. They are repeated
here because each one changes what you build:
1. **Livewire is `^4.3`, not 3.** `CLAUDE.md` says Livewire 3 and is wrong. In Livewire 4 a
plain `public` property is settable from the browser, so validating an id in `mount()`
and then filtering queries on it in `render()` is an IDOR. Use `#[Locked]` **and**
re-validate.
2. **`dark:` is not inert — it is live and untested.** With no `@custom-variant` declared,
Tailwind v4 compiles `dark:` to `prefers-color-scheme`, so ~900 utilities already render
for every OS-dark user. The `.dark` class toggle is the part that does nothing.
3. **`transactions` is a single row** with `debit_account_id` + `credit_account_id` — not a
debit/credit pair. `CLAUDE.md` and two files in `docs/agent-rules/` describe a schema
that does not exist. The migration is the truth.
4. **The domain is largely already built.** See below.
---
## Already built — do not rebuild
The single biggest risk on this programme is writing something that exists. Verified
present:
- **A generic block-builder engine**`app/Domain/Website/Blocks/` (`BlockType`,
`BlockField`, `BlockRegistry`, 31 block types) plus the generic field renderer
`resources/views/website/builder/field.blade.php`. A new content type needs **only a PHP
class** — no migration, no enum, no CHECK constraint, no form code. The addendum's
recommendation is to reuse this and **not** build a second CMS.
- **A parent portal** — 11 Livewire components in `app/Livewire/Parent/`, mobile-shaped
layout `resources/views/layouts/parent.blade.php`, routed under `/parent`. This is the
migration source for the new portal, and it gets retired at parity.
- **FCM push**`PushNotificationService`, 12 event listeners, `device_tokens`,
`push_announcements`, `push_analytics`, scheduled reminder commands.
- **Domain services the portal reads from** — pricing (`PricingService::calculate()`, which
already accepts a `contextOverride` for pricing before a participant row exists),
enrolment, attendance, invoicing, wallets, installments, `service_requests`,
`contact_messages`, events with dynamic form fields, documents, evaluations.
- **WhatsApp sending**`WhatsAppService` via an external messaging hub.
- **Paymob**`PaymobService`, feature-flagged off.
---
## Superseded documents — actively misleading now
| Document | Why it is wrong |
|---|---|
| `docs/specs/mobile-app-plan.md` | Plans a **native Flutter app per client** against a REST API. The approach changed to a web portal + WebView shell, and **the API it describes has been deleted**. |
| `docs/specs/mobile-api-implementation.md` | Implementation plan for that same deleted API. |
| `docs/api/openapi.yaml` | Contract for endpoints that no longer exist. |
They are kept as history. Do not build from them.
---
## Build order (from the addendum — this replaces the plan's own)
```
S0 production safety ...................... DONE
S1 data integrity + the ledger ............ blocks all money and push UI
S2 branding + BrandingService ............. parallel with S1
S3 identity, roles, invitations, OTP ...... hard dependency for S4–S10
S4 portal shell + read-only screens ....... needs S2 + S3
S5 InstaPay payments ...................... needs S1 + S4
S6 PWA ................................... needs S2 + S4
S7 push to the portal ..................... needs S1 + S3 + S6
S8 QR check-in (staff-scan only) .......... needs S1 + S3 + S4
S9 Flutter wrapper ........................ needs S6 + S7 working in a browser
S10 app content ............................ no dependents, last
```
**S1 is not cleanup.** `PaymentService::resolveDebitAccount()` and `resolveCreditAccount()`
return hardcoded `1` and `2` with a `// TODO`, so every payment ever recorded posts to the
same two accounts. Routing a new payment channel into that ledger just multiplies the
problem.
---
## Decisions already made with the client
| Decision | Choice |
|---|---|
| Portal login | Admin-issued invitation + password; phone or email + password after |
| Accounts | One account, many linked profiles, with a profile switcher |
| QR check-in | Staff-scan only (the static-poster direction was cut — see addendum) |
| Notifications | PWA + push, reusing the existing FCM stack (VAPID was cut) |
| Existing `/parent` | Replace it, redirect, then retire |
| Non-members | Full self-registration — which makes phone verification mandatory |
| REST API v1 | Deleted |
## Still open — need a human answer
E1 dark mode (own it or delete it) · E2 age of majority for self-service · E3 whether the
Flutter shell ships this cycle at all · E4 duplicate-account merge · E5 overpaid proofs ·
E6 which payment-method CHECK constraints get `instapay` · E7 where an excuse lives ·
E8 branch attribution on portal payments.
Each is written up with a recommendation in section E of the addendum.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment