1. 25 Oct, 2026 3 commits
    • Mahmoud Aglan's avatar
      fix(tournaments): bot testing can no longer touch a real tournament · 1f8152ac
      Mahmoud Aglan authored
      Populate used to attach a new Swiss-service tournament to whatever it was
      pointed at, which switched a game-run tournament to the other engine, and
      registered bots with no profile that real players were then paired against.
      Population is refused once a real player has registered or the tournament
      has left draft, and simulation while any real player is entered, unless a
      superadmin ticks an explicit test override. Game-run test tournaments are
      simulated natively: start through the game, then results drawn from the
      bots' ratings are recorded through the game's arbiter endpoint and the
      engine pairs the next round itself. bot_metadata is stored as jsonb, not a
      JSON string.
      Co-Authored-By: 's avatarClaude Opus 5.5 <noreply@anthropic.com>
      1f8152ac
    • Mahmoud Aglan's avatar
      fix(tournaments): the manager runs the tournaments the game actually plays · aa025be9
      Mahmoud Aglan authored
      Create and edit write real el3ab_tournaments columns. Edit used to send
      organization_id, rounds_count, entry_fee, prize_pool, start_date and
      end_date (none exist) and complete sent completed_at, so PostgREST rejected
      both and the page said saved. The form now also covers name_ar, min_players,
      min/max rating, is_rated, auto_start, the registration window and a real
      prize distribution ([{place, coins}]), validates them, and reads and writes
      times in Cairo (stored as UTC, DST included). Editing is allowed while
      registration is open; the edit page no longer crashes on phase_config.
      
      No more Swiss linking on create: picking an organisation used to create a
      Swiss-service tournament and hand the event to a second engine.
      
      Tournaments the player app runs (no swiss_api_tournament_id) are shown from
      el3ab_tournament_rounds.pairings through NativeTournamentService: every
      board with names, results, reasons and who set them; standings with the
      engine's own scoring (aborted boards are losses, per-time-control rating)
      and the chosen tiebreak columns; the players tab resolves names from
      profiles (there is no player_name column) including withdrawn players.
      
      Arbiter tools relay to the game's api/tournament-admin.php with the service
      key: set or override a result, forfeit (either side or both), next round,
      close the round now (unfinished boards 0-0), pause/resume, end early with
      final placings, add a player by username (late entry once running),
      withdraw, bulk add by usernames. Start goes through the game so the
      minimum-players check is no longer skipped. Tiebreaks save directly for
      these tournaments and offer only the four keys the engine computes, under
      the names the app shows players.
      Co-Authored-By: 's avatarClaude Opus 5.5 <noreply@anthropic.com>
      aa025be9
    • Mahmoud Aglan's avatar
      fix(core): a write PostgREST rejects can no longer be reported as saved · d5bedc75
      Mahmoud Aglan authored
      Database used to decode the PostgREST error body and hand it back as if it
      were the written row, so writes to missing columns (PGRST204), bad types and
      broken foreign keys came back truthy and the controller flashed success.
      Every failed request is now recorded and logged; insert/update/rpc return
      null on failure; Response::success, redirect-with-success and json refuse to
      report success over a recorded write failure and show the database's reason.
      Best-effort writes (audit trail, page-view counters, last login) go through
      Database::soft() so they cannot fail the action they describe.
      
      AuditLog had never written a row: staff ids broke the actor_id foreign key
      to profiles, names broke the resource_id uuid, and details was double
      encoded. Staff now go in admin_user_id and text ids in resource_ref
      (El3ab migrations/2026-10-26-manager-columns.sql); the audit page shows the
      admin's name and searches the text reference.
      
      Staff type times in Cairo: the app now runs in Africa/Cairo and core/Time
      converts typed times to UTC instants and back, DST included.
      Co-Authored-By: 's avatarClaude Opus 5.5 <noreply@anthropic.com>
      d5bedc75
  2. 01 Sep, 2026 3 commits
    • Mahmoud Aglan's avatar
      docs: playbook for running a Swiss + top-8 knockout championship · aeeb1740
      Mahmoud Aglan authored
      Covers hosting, announcing and monetising the format end to end, verified
      against the live code and database rather than inferred from column names.
      
      The headline finding is that the built-in multi-phase path — the preset
      literally labelled سويسري ← إقصاء — cannot work: startSwissPhase reads a
      swiss_event_id column that does not exist, startPhase merges that error into a
      success envelope so it fails silently, getQualifiedPlayers can only read the
      external Swiss service, and tournamentFinish completes the tournament before
      the advance buttons can be pressed. tournament_phases, tournament_brackets and
      bracket_matches all hold zero rows in production; it has never run.
      
      The document gives the path that does work — the Swiss natively, a manual cut,
      and the knockout as a second tournament whose phase 1 is the bracket, which is
      reachable because getPhasePlayers reads registrations directly for phase 1 —
      and is blunt about entry fees and prizes being displayed but never moved.
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      aeeb1740
    • Mahmoud Aglan's avatar
      fix(live): give Swiss tournaments their player names back · bc768f8b
      Mahmoud Aglan authored
      The external Swiss service returns pairings carrying whitePlayerId and
      blackPlayerId and no names at all, so every board on a Swiss tournament
      rendered "لاعب" against "لاعب" and the rounds timeline showed a column of
      question marks. Its standings response does carry names, so the two are joined
      in LiveDataService and both engines' pairings are normalised to one shape
      before any view sees them — which is also where the array-into-string fatals
      on this page came from.
      
      Its stats were hardcoded zeros: a finished five-round, 64-player championship
      reported that no game had ever been played, and the vitals section fell through
      to "nothing has been played yet". The counts are now derived from the rounds.
      
      Tiebreaks arrive as an ordered tiebreakValues list rather than named columns;
      read either, and drop the column entirely when a tournament has none rather
      than printing a column of em-dashes.
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      bc768f8b
    • Mahmoud Aglan's avatar
      fix(live): make the manager show the tournaments EL3AB actually runs · 4a2cb04f
      Mahmoud Aglan authored
      The manager could only ever see the external Swiss service. Every tournament
      the player app's own engine ran was invisible here: standings came back empty,
      pairings were read from a column that does not exist (swiss_api_round_id,
      against a swiss_round_id column), and three of the fallback tables the service
      queried have never existed in this schema.
      
      The public live page was also fatal. The Swiss service wraps its rows in
      {"data": [...]}; the standings view iterated the wrapper, so "data" + 1
      crashed PHP 8 and printed a stack trace with server paths to anyone holding the
      link. Two more views did the same thing to the native pairing shape, which puts
      an object where the Swiss shape puts a name.
      
      - NativeTournamentService reads the engine's rounds and computes standings,
        pairings and tiebreaks from them; LiveDataService is native-first throughout.
      - display_errors is off in the image, so a fatal is logged, never rendered.
      - Start no longer flips a tournament to in_progress and leaves it unpaired, and
        Generate Round no longer answers "no link to the Swiss system" for every
        tournament EL3AB runs. Both ask the player app's engine, authenticating with
        the service key both apps already hold.
      
      Two controls on the live page were scenery. The player-compare selects were
      filled from a class the players view no longer emits, and choosing two names ran
      a function whose whole body hid the result; the player card modal needed a
      data-player payload nothing rendered. Both now work off data already on the page.
      
      Visually the page was rebuilt around what a spectator opens it for: the boards
      and the leaderboard first (the share box and compare tool used to be pulled
      above them on a phone by order:-1), a podium, avatars and form dots, board cards
      that show at a glance which games are still running, a readable rounds timeline,
      a knockout bracket with connectors, and a QR that leads the share card instead of
      hiding behind a disclosure. Sections no longer render at opacity 0 when script
      does not run.
      
      tools/verify-cycle.mjs runs the whole cycle across both apps and checks the
      manager reports what the players did; it passes.
      Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
      4a2cb04f
  3. 03 Jul, 2026 2 commits
  4. 22 Jun, 2026 1 commit
  5. 21 Jun, 2026 6 commits
  6. 06 Jun, 2026 3 commits
  7. 05 Jun, 2026 1 commit
  8. 31 May, 2026 1 commit
  9. 30 May, 2026 15 commits
  10. 26 May, 2026 2 commits
    • Mahmoud Aglan's avatar
      fix: replace Auth::csrfField() with Auth::csrfToken() + fix org-leaderboards view mismatch · 0dc3c835
      Mahmoud Aglan authored
      Auth::csrfField() doesn't exist — replaced all 24 occurrences across org modules
      with the correct <input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">.
      Also fixed org-leaderboards index.php which was actually the org-board view (using
      $org variable that wasn't passed by the index action), renamed it to org-board.php
      and created a proper index.php for global rankings. Fixed org_members column name
      (user_id → player_id) in org-applications controller.
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      0dc3c835
    • Mahmoud Aglan's avatar
      feat: Live Tournaments Update — public tournament pages with 40 features · e79d4439
      Mahmoud Aglan authored
      Full public-facing live tournament system:
      - Public pages at /live/{slug} with no auth required
      - 5 visual themes (default, neon, minimal, royal, arena)
      - Live standings, pairings, bracket with auto-refresh polling
      - Countdown timer, player roster, results feed, timeline
      - Prize display, announcements feed, photo gallery
      - QR code sharing, social share, embeddable widget
      - SEO meta tags (OG, Twitter Cards, JSON-LD)
      - Tournament ad slots with impression/click tracking
      - Analytics tracking (views, sections, referrers)
      - Admin config panel: visibility toggles, custom CSS, slug management
      - Mobile responsive with dark/light mode support
      - Print-optimized view
      
      New DB tables: tournament_announcements, tournament_ad_slots,
      tournament_page_views, tournament_media
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      e79d4439
  11. 25 May, 2026 3 commits
    • Mahmoud Aglan's avatar
      fix: update theme to El3ab brand identity + add reset button · 853341b5
      Mahmoud Aglan authored
      - Update variables.css surfaces to match brand navy (#152132)
      - Add sidebar-bg and input-bg CSS variables with fallbacks
      - Add "Reset to Brand Identity" button in branding settings
      - Add resetColors controller action that reverts all colors to
        their default_value from platform_theme
      - Reset live DB colors to brand defaults
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      853341b5
    • Mahmoud Aglan's avatar
      feat: organizational steroids update — 18 new modules + bugfixes · c08ba733
      Mahmoud Aglan authored
      - Add 18 org ecosystem modules: frames, invites, applications, chat,
        treasury, challenges, achievements, transfers, recruitment, events,
        content, leaderboards, partnerships, loyalty, rosters, training,
        media, spotlights (controllers + views + CSS assets)
      - Add core services: SupabaseStorage, OrgPermissions, ThemeService
      - Add migration 003 (23 new tables, RLS policies, indexes)
      - Fix games edit not saving (wrong column names: icon→icon_url,
        matchmaking_config→config)
      - Fix branding colors not reflecting (added ThemeService to inject
        DB theme colors as CSS variable overrides at render time)
      - Update permissions.php with new module access for admin/org roles
      - Update sidebar with org ecosystem navigation section
      - Add 120+ new routes for all org modules
      Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
      c08ba733
    • Mahmoud Aglan's avatar
      tournament update · 8b678c5b
      Mahmoud Aglan authored
      8b678c5b