{
  "ok": true,
  "count": 200,
  "total": 253,
  "generatedAt": "2026-04-27T17:36:26.408Z",
  "entries": [
    {
      "date": "2026-04-25",
      "iter": null,
      "kind": "note",
      "title": "LIVE — almajlis.om in production. Cloudflare zone 7c387f3ba373780002dbb9968fd44aa6 (active, paul/robin NS), DNS records: A almajlis.om→147.93.20.54 (proxied) + A *.almajlis.om→147.93.20.54 (wildcard, proxied) + CNAME www→apex (proxied). SSL: Flexible (CF→nginx HTTP). nginx vhost at /www/server/panel/vhost/nginx/almajlis.om.conf serving almajlis.om + www + *.almajlis.om → port 4100 (fencing-om PM2 app). Tenant 'almajlis' registered in data/tenants.json with plan:enterprise. DialogueME font family (8 WOFF2 weights) + logo SVGs (mark/horizontal/stacked) + brand.css from Claude Design deliverable now under public/. Verified: https://almajlis.om/api/tenant returns slug=almajlis, title 'AlMajlis — المجلس', brand assets 200, fencing.om unaffected (96/96 smoke green). Next: install CF Origin Cert + nginx 443 block to upgrade to Full(strict); then integrate Claude Design's brand-system.html as the actual marketing site (currently the AlMajlis tenant renders the same shell as default tenant); then Phase 8 (API keys + webhooks + Paymob billing + onboarding wizard)",
      "raw": "2026-04-25 · phase4.6.5 · LIVE — almajlis.om in production. Cloudflare zone 7c387f3ba373780002dbb9968fd44aa6 (active, paul/robin NS), DNS records: A almajlis.om→147.93.20.54 (proxied) + A *.almajlis.om→147.93.20.54 (wildcard, proxied) + CNAME www→apex (proxied). SSL: Flexible (CF→nginx HTTP). nginx vhost at /www/server/panel/vhost/nginx/almajlis.om.conf serving almajlis.om + www + *.almajlis.om → port 4100 (fencing-om PM2 app). Tenant 'almajlis' registered in data/tenants.json with plan:enterprise. DialogueME font family (8 WOFF2 weights) + logo SVGs (mark/horizontal/stacked) + brand.css from Claude Design deliverable now under public/. Verified: https://almajlis.om/api/tenant returns slug=almajlis, title 'AlMajlis — المجلس', brand assets 200, fencing.om unaffected (96/96 smoke green). Next: install CF Origin Cert + nginx 443 block to upgrade to Full(strict); then integrate Claude Design's brand-system.html as the actual marketing site (currently the AlMajlis tenant renders the same shell as default tenant); then Phase 8 (API keys + webhooks + Paymob billing + onboarding wizard)"
    },
    {
      "date": "2026-04-25",
      "iter": null,
      "kind": "note",
      "title": "SHIPPED — The Majlis product brand. Product identity doc at docs/brand/the-majlis-identity.md (positioning, voice, color tokens, typography, copy samples, messaging pillars). Logo SVGs at public/brand/ (mark, wordmark, AR-wordmark, reversed, favicon). DEFAULT_BRANDING updated to neutral Majlis shell (teal-green #1e6b55, brass gold accent, 'The Majlis' name, themajlis.om primary domain). OFC explicitly preserves fencing.om identity via data/tenants/ofc/branding.json (green #1e8439, 'Oman Fencing Committee', /logo.png, fencing.om). Sport-specific modules (athletes + selections) now defaultEnabled:false — OFC re-enables via data/tenants/ofc/features.json. src/pages/index.astro stopped hardcoding OFC title. Verified: fencing.om prod title + theme-color unchanged; default tenant lands on 'The Majlis — المجلس' shell. 96/96 smoke green. Domains to register: themajlis.om (10 OMR, available at GulfCyberTech), themajlis.com, themajlis.app",
      "raw": "2026-04-25 · phase4.5 · SHIPPED — The Majlis product brand. Product identity doc at docs/brand/the-majlis-identity.md (positioning, voice, color tokens, typography, copy samples, messaging pillars). Logo SVGs at public/brand/ (mark, wordmark, AR-wordmark, reversed, favicon). DEFAULT_BRANDING updated to neutral Majlis shell (teal-green #1e6b55, brass gold accent, 'The Majlis' name, themajlis.om primary domain). OFC explicitly preserves fencing.om identity via data/tenants/ofc/branding.json (green #1e8439, 'Oman Fencing Committee', /logo.png, fencing.om). Sport-specific modules (athletes + selections) now defaultEnabled:false — OFC re-enables via data/tenants/ofc/features.json. src/pages/index.astro stopped hardcoding OFC title. Verified: fencing.om prod title + theme-color unchanged; default tenant lands on 'The Majlis — المجلس' shell. 96/96 smoke green. Domains to register: themajlis.om (10 OMR, available at GulfCyberTech), themajlis.com, themajlis.app"
    },
    {
      "date": "2026-04-24",
      "iter": null,
      "kind": "note",
      "title": "SHIPPED — per-tenant branding. Task 4.1 src/lib/branding.ts with Branding interface + DEFAULT_BRANDING (byte-for-byte match of current OFC hardcoded values) + readBranding/setBranding + brandingToCssVars + validators. Task 4.2 Base.astro swaps every hardcoded brand ref (title/meta author/RSS titles/og:site_name/JSON-LD/favicon/theme-color/siteUrl/fontColor etc) for branding-derived values; CSS vars injected via <style is:inline set:html={cssVars}> before global.css. Task 4.3 Branding editor section in SettingsTab (Identity/Logo&Favicon/Colors/Typography) + /api/board/branding/update POST endpoint with admin gate + hex/URL validation + atomic withFileLock writes. Commit 383a301. Runtime verified: <title> still 'Oman Fencing Committee — اللجنة العمانية للمبارزة' and theme-color=#1e8439 with NO branding.json written (defaults active). 96/96 smoke green. Task 4.4 (nginx+certbot custom-domain automation) deferred to Phase 4b — primaryDomain + emailFrom fields ship as data shape now",
      "raw": "2026-04-24 · phase4 · SHIPPED — per-tenant branding. Task 4.1 src/lib/branding.ts with Branding interface + DEFAULT_BRANDING (byte-for-byte match of current OFC hardcoded values) + readBranding/setBranding + brandingToCssVars + validators. Task 4.2 Base.astro swaps every hardcoded brand ref (title/meta author/RSS titles/og:site_name/JSON-LD/favicon/theme-color/siteUrl/fontColor etc) for branding-derived values; CSS vars injected via <style is:inline set:html={cssVars}> before global.css. Task 4.3 Branding editor section in SettingsTab (Identity/Logo&Favicon/Colors/Typography) + /api/board/branding/update POST endpoint with admin gate + hex/URL validation + atomic withFileLock writes. Commit 383a301. Runtime verified: <title> still 'Oman Fencing Committee — اللجنة العمانية للمبارزة' and theme-color=#1e8439 with NO branding.json written (defaults active). 96/96 smoke green. Task 4.4 (nginx+certbot custom-domain automation) deferred to Phase 4b — primaryDomain + emailFrom fields ship as data shape now"
    },
    {
      "date": "2026-04-24",
      "iter": null,
      "kind": "note",
      "title": "SHIPPED — role+permission matrix. Task 3.1+3.2 src/lib/roles.ts with hasPermission() + legacy fallback + createTenant seeds admin/coach/member (56de008). Task 3.4 permission editor UI (modules x actions checkbox grid) + /api/board/roles/{update,create,delete} endpoints with seeded-role protection + admin wildcard safety net (fde24db). 12/12 permission cases pass, 96/96 smoke green. Reviewer SHIP. Tasks 3.3 (wire hasPermission into every tab ~20 tabs) + 3.5 (user migration) deferred — OFC runs on legacy fallback with zero behavior change until Phase 3.3. Phase 2 polish shipped earlier (bc6629f): withFileLock on features.ts, AccountsTab superadmin guard, err flash on home, AR error translations",
      "raw": "2026-04-24 · phase3 · SHIPPED — role+permission matrix. Task 3.1+3.2 src/lib/roles.ts with hasPermission() + legacy fallback + createTenant seeds admin/coach/member (56de008). Task 3.4 permission editor UI (modules x actions checkbox grid) + /api/board/roles/{update,create,delete} endpoints with seeded-role protection + admin wildcard safety net (fde24db). 12/12 permission cases pass, 96/96 smoke green. Reviewer SHIP. Tasks 3.3 (wire hasPermission into every tab ~20 tabs) + 3.5 (user migration) deferred — OFC runs on legacy fallback with zero behavior change until Phase 3.3. Phase 2 polish shipped earlier (bc6629f): withFileLock on features.ts, AccountsTab superadmin guard, err flash on home, AR error translations"
    },
    {
      "date": "2026-04-24",
      "iter": null,
      "kind": "note",
      "title": "SHIPPED — module registry + per-tenant feature flags. Task 2.1+2.2 MODULE_REGISTRY (28 modules after settings added) + features.ts store (4797a2a + e460917). Task 2.3 board.astro swaps 27-entry hardcoded MODULE_GROUPS for enabledGroupsForRole(me.role), VALID_TABS now registry-derived, badges routed through moduleBadges.ts (7a8ff64). Task 2.4 /board?t=settings toggle grid + /api/board/features/toggle endpoint with self-lockout protection for settings+overview slugs (8473052). 96/96 smoke green. Follow-ups: wrap features.ts writes in withFileLock, AccountsTab role guard on line 239 doesn't include superadmin, err flash on board home, AR translation of err messages in SettingsTab",
      "raw": "2026-04-24 · phase2 · SHIPPED — module registry + per-tenant feature flags. Task 2.1+2.2 MODULE_REGISTRY (28 modules after settings added) + features.ts store (4797a2a + e460917). Task 2.3 board.astro swaps 27-entry hardcoded MODULE_GROUPS for enabledGroupsForRole(me.role), VALID_TABS now registry-derived, badges routed through moduleBadges.ts (7a8ff64). Task 2.4 /board?t=settings toggle grid + /api/board/features/toggle endpoint with self-lockout protection for settings+overview slugs (8473052). 96/96 smoke green. Follow-ups: wrap features.ts writes in withFileLock, AccountsTab role guard on line 239 doesn't include superadmin, err flash on board home, AR translation of err messages in SettingsTab"
    },
    {
      "date": "2026-04-24",
      "iter": null,
      "kind": "note",
      "title": "SHIPPED — multi-tenant unlock live. Task 1.1 tenant registry (67d8e85), 1.2 middleware+AsyncLocalStorage (658dac6), 1.3 33 stores refactored to tenantReadPath/tenantWritePath (70bf41f), 1.4 migration script + 5 files copied to data/tenants/ofc/ (c9c049f), 1.6 /admin/tenants superadmin page + scripts/set-superadmin.mjs (220c75c). Ali promoted to superadmin on prod. /api/tenant returns {slug:ofc} for fencing.om. 96/96 smoke green. Phase 1b follow-ups: tenant-scope content/ dir before onboarding tenant #2, tenant-scope public/uploads/board/, tenant-scope src/lib/referees.ts. Task 1.5 (retire legacy data/*.json) intentionally deferred for 7-day soak",
      "raw": "2026-04-24 · phase1 · SHIPPED — multi-tenant unlock live. Task 1.1 tenant registry (67d8e85), 1.2 middleware+AsyncLocalStorage (658dac6), 1.3 33 stores refactored to tenantReadPath/tenantWritePath (70bf41f), 1.4 migration script + 5 files copied to data/tenants/ofc/ (c9c049f), 1.6 /admin/tenants superadmin page + scripts/set-superadmin.mjs (220c75c). Ali promoted to superadmin on prod. /api/tenant returns {slug:ofc} for fencing.om. 96/96 smoke green. Phase 1b follow-ups: tenant-scope content/ dir before onboarding tenant #2, tenant-scope public/uploads/board/, tenant-scope src/lib/referees.ts. Task 1.5 (retire legacy data/*.json) intentionally deferred for 7-day soak"
    },
    {
      "date": "2026-04-24",
      "iter": null,
      "kind": "note",
      "title": "SHIPPED — Phase 0 of productize-board-workspace master plan complete. Task 0.1 SurveysTab wired (eba1294), Task 0.2 ActionsTab+ActionDrawer on canonical commentStore (f9b7501), Task 0.3 ApprovalsTab+ApprovalDrawer (b0c45f9), Task 0.4 AccountsTab full CRUD invite/disable/enable/revoke + disabled-user session block (75a1f7a). Final code review SHIP verdict. Known follow-ups: ApprovalsList row openers not wired to drawers, overdueAll dead code in board.astro, CSRF hardening pass due. 96/96 smoke green",
      "raw": "2026-04-24 · phase0 · SHIPPED — Phase 0 of productize-board-workspace master plan complete. Task 0.1 SurveysTab wired (eba1294), Task 0.2 ActionsTab+ActionDrawer on canonical commentStore (f9b7501), Task 0.3 ApprovalsTab+ApprovalDrawer (b0c45f9), Task 0.4 AccountsTab full CRUD invite/disable/enable/revoke + disabled-user session block (75a1f7a). Final code review SHIP verdict. Known follow-ups: ApprovalsList row openers not wired to drawers, overdueAll dead code in board.astro, CSRF hardening pass due. 96/96 smoke green"
    },
    {
      "date": "2026-04-24",
      "iter": 258,
      "kind": "phase0",
      "title": "Phase 0 Task 0.1 — wire orphaned SurveysTab into /board MODULE_GROUPS Governance group (after Polls, before Decisions); 385-line tab was dead code until this commit. Badge counts open surveys",
      "raw": "2026-04-24 · iter 258 · phase0 · Phase 0 Task 0.1 — wire orphaned SurveysTab into /board MODULE_GROUPS Governance group (after Polls, before Decisions); 385-line tab was dead code until this commit. Badge counts open surveys"
    },
    {
      "date": "2026-04-24",
      "iter": 257,
      "kind": "ux",
      "title": "/board home cmd-K palette now actually works — previously the kbd hint was decorative because ModuleHeader (which owned the keydown handler) only mounted on tab views. AppGrid now ships a self-contained palette (search input, arrow-nav, enter, esc, click-outside); keybind auto-detects Mac (⌘K) vs Win/Linux (Ctrl K) via userAgentData.platform fallback to navigator.platform — the kbd label flips on mount. Typing-aware (ignored inside INPUT/TEXTAREA/contenteditable)",
      "raw": "2026-04-24 · iter 257 · ux · /board home cmd-K palette now actually works — previously the kbd hint was decorative because ModuleHeader (which owned the keydown handler) only mounted on tab views. AppGrid now ships a self-contained palette (search input, arrow-nav, enter, esc, click-outside); keybind auto-detects Mac (⌘K) vs Win/Linux (Ctrl K) via userAgentData.platform fallback to navigator.platform — the kbd label flips on mount. Typing-aware (ignored inside INPUT/TEXTAREA/contenteditable)"
    },
    {
      "date": "2026-04-24",
      "iter": 256,
      "kind": "maintenance",
      "title": "smoke.sh 90->96 endpoints — covers iter 248 (/api/public/stats/birthyears), iter 255 (/api/public/stats/weapons), iter 250/251 (?ageCategory=U20, Senior, csv). 96/96 green on prod",
      "raw": "2026-04-24 · iter 256 · maintenance · smoke.sh 90->96 endpoints — covers iter 248 (/api/public/stats/birthyears), iter 255 (/api/public/stats/weapons), iter 250/251 (?ageCategory=U20, Senior, csv). 96/96 green on prod"
    },
    {
      "date": "2026-04-24",
      "iter": 255,
      "kind": "api",
      "title": "/api/public/stats/weapons per-weapon aggregate — 3 rows (foil/epee/sabre) with athletes count, gender split, and gold/silver/bronze tallied from results store. Foil=7 (4M/3F), epee+sabre follow. First SSH deploy stalled on the Bash tool; fresh retry succeeded. Registered in feeds/openapi/developers",
      "raw": "2026-04-24 · iter 255 · api · /api/public/stats/weapons per-weapon aggregate — 3 rows (foil/epee/sabre) with athletes count, gender split, and gold/silver/bronze tallied from results store. Foil=7 (4M/3F), epee+sabre follow. First SSH deploy stalled on the Bash tool; fresh retry succeeded. Registered in feeds/openapi/developers"
    },
    {
      "date": "2026-04-24",
      "iter": 254,
      "kind": "ux",
      "title": "FIE age-category mini-chip on /athletes grid cards (top-right, charcoal with 90% opacity, same title a11y pattern as iter 252/253); renders only when birthYear is known. Verified 3 U20 card chips live",
      "raw": "2026-04-24 · iter 254 · ux · FIE age-category mini-chip on /athletes grid cards (top-right, charcoal with 90% opacity, same title a11y pattern as iter 252/253); renders only when birthYear is known. Verified 3 U20 card chips live"
    },
    {
      "date": "2026-04-24",
      "iter": 253,
      "kind": "ux",
      "title": "FIE age-category chip on athlete detail page — inline next to the Born/Age line, charcoal-pill theme matching iter 252 filter row, title='FIE age category {X}' for a11y. Verified U20 chip renders on /athletes/israa-al-siyabi",
      "raw": "2026-04-24 · iter 253 · ux · FIE age-category chip on athlete detail page — inline next to the Born/Age line, charcoal-pill theme matching iter 252 filter row, title='FIE age category {X}' for a11y. Verified U20 chip renders on /athletes/israa-al-siyabi"
    },
    {
      "date": "2026-04-24",
      "iter": 252,
      "kind": "ux",
      "title": "FIE age-category filter pills on /athletes (All/U11/U13/U15/U17/U20/Senior); charcoal-theme to visually distinguish from the green discipline pills above; combines with discipline+category+club+search filters in client-side applyFilters; reset button re-initialises both. Verified pills + data-age='U20' on cards live",
      "raw": "2026-04-24 · iter 252 · ux · FIE age-category filter pills on /athletes (All/U11/U13/U15/U17/U20/Senior); charcoal-theme to visually distinguish from the green discipline pills above; combines with discipline+category+club+search filters in client-side applyFilters; reset button re-initialises both. Verified pills + data-age='U20' on cards live"
    },
    {
      "date": "2026-04-24",
      "iter": 251,
      "kind": "api",
      "title": "/athletes.csv gains birth_year + age_eoy + age_category columns + ?ageCategory filter (parity with iter 250 JSON). Filename suffix reflects filter (ofc-athletes-u20.csv). Verified 3 U20 rows returned with age 18/18/17 and correct category",
      "raw": "2026-04-24 · iter 251 · api · /athletes.csv gains birth_year + age_eoy + age_category columns + ?ageCategory filter (parity with iter 250 JSON). Filename suffix reflects filter (ofc-athletes-u20.csv). Verified 3 U20 rows returned with age 18/18/17 and correct category"
    },
    {
      "date": "2026-04-24",
      "iter": 250,
      "kind": "api",
      "title": "MILESTONE 250 iters. ?ageCategory=U11|U13|U15|U17|U20|Senior filter on /api/public/athletes (case-insensitive exact match against the iter 249 ageCategory field; athletes without birthYear never match). Verified U20=3 (all 3 seeded), Senior=0. /api/public index endpoint descriptor updated",
      "raw": "2026-04-24 · iter 250 · api · MILESTONE 250 iters. ?ageCategory=U11|U13|U15|U17|U20|Senior filter on /api/public/athletes (case-insensitive exact match against the iter 249 ageCategory field; athletes without birthYear never match). Verified U20=3 (all 3 seeded), Senior=0. /api/public index endpoint descriptor updated"
    },
    {
      "date": "2026-04-24",
      "iter": 249,
      "kind": "api",
      "title": "enrich /api/public/athletes rows with birthYear + ageEoY + ageCategory (FIE end-of-year rule); extract to shared lib/ageCategory.ts (DRY with /api/public/stats/birthyears from iter 248). Athletes without birthYear get null values. Verified weapon=foil row → age 18, category U20, birthYear 2008",
      "raw": "2026-04-24 · iter 249 · api · enrich /api/public/athletes rows with birthYear + ageEoY + ageCategory (FIE end-of-year rule); extract to shared lib/ageCategory.ts (DRY with /api/public/stats/birthyears from iter 248). Athletes without birthYear get null values. Verified weapon=foil row → age 18, category U20, birthYear 2008"
    },
    {
      "date": "2026-04-24",
      "iter": 248,
      "kind": "api",
      "title": "/api/public/stats/birthyears athlete age distribution — FIE categories (U11/U13/U15/U17/U20/Senior, end-of-year rule), dense per-year array (gaps kept as count=0), filtered to active athletes with birthYear. 3 active athletes seeded with birthYear, all U20 (2008 x2 + 2009 x1). Registered in feeds/openapi/developers",
      "raw": "2026-04-24 · iter 248 · api · /api/public/stats/birthyears athlete age distribution — FIE categories (U11/U13/U15/U17/U20/Senior, end-of-year rule), dense per-year array (gaps kept as count=0), filtered to active athletes with birthYear. 3 active athletes seeded with birthYear, all U20 (2008 x2 + 2009 x1). Registered in feeds/openapi/developers"
    },
    {
      "date": "2026-04-24",
      "iter": 247,
      "kind": "maintenance",
      "title": "smoke.sh 85->94 endpoints — adds /api/public/stats/clubs (iter 245), /events/upcoming.ics (iter 246), /leaderboard.csv + 3 leaderboard?weapon permutations (iter 244). Spot-verified all 3 new endpoints return 200 on prod",
      "raw": "2026-04-24 · iter 247 · maintenance · smoke.sh 85->94 endpoints — adds /api/public/stats/clubs (iter 245), /events/upcoming.ics (iter 246), /leaderboard.csv + 3 leaderboard?weapon permutations (iter 244). Spot-verified all 3 new endpoints return 200 on prod"
    },
    {
      "date": "2026-04-24",
      "iter": 246,
      "kind": "api",
      "title": "/events/upcoming.ics future-only iCalendar feed (RFC 5545 VCALENDAR; filters date<todayUTC; sorts asc; X-WR-CALNAME 'Oman Fencing — Upcoming'); registered in feeds.json + /developers. First prod call 500'd (pm2 warm-up per feedback_pm2_restart_502_warmup); 200 on retry with empty VCALENDAR body since all seeded events are past-dated, correct behaviour",
      "raw": "2026-04-24 · iter 246 · api · /events/upcoming.ics future-only iCalendar feed (RFC 5545 VCALENDAR; filters date<todayUTC; sorts asc; X-WR-CALNAME 'Oman Fencing — Upcoming'); registered in feeds.json + /developers. First prod call 500'd (pm2 warm-up per feedback_pm2_restart_502_warmup); 200 on retry with empty VCALENDAR body since all seeded events are past-dated, correct behaviour"
    },
    {
      "date": "2026-04-24",
      "iter": 245,
      "kind": "api",
      "title": "/api/public/stats/clubs per-club athlete aggregates (total/active + foil/epee/sabre + male/female), sorted by active count desc. Uses tolerant club-name normalisation (Quriyat→Quryat) + NFD weapon/gender match. Top club Salalah (3 active: 1 foil + 2 epee, all female). 8 clubs. Registered in feeds/openapi/developers",
      "raw": "2026-04-24 · iter 245 · api · /api/public/stats/clubs per-club athlete aggregates (total/active + foil/epee/sabre + male/female), sorted by active count desc. Uses tolerant club-name normalisation (Quriyat→Quryat) + NFD weapon/gender match. Top club Salalah (3 active: 1 foil + 2 epee, all female). 8 clubs. Registered in feeds/openapi/developers"
    },
    {
      "date": "2026-04-24",
      "iter": 244,
      "kind": "api",
      "title": "?weapon=foil|epee|sabre|mixed filter on /api/public/leaderboard + /leaderboard.csv (case-insensitive, runs through medalLeaderboard signature change). JSON echoes weapon field, CSV filename concatenates year+weapon (e.g. ofc-leaderboard-2025-epee.csv). Verified prod",
      "raw": "2026-04-24 · iter 244 · api · ?weapon=foil|epee|sabre|mixed filter on /api/public/leaderboard + /leaderboard.csv (case-insensitive, runs through medalLeaderboard signature change). JSON echoes weapon field, CSV filename concatenates year+weapon (e.g. ofc-leaderboard-2025-epee.csv). Verified prod"
    },
    {
      "date": "2026-04-23",
      "iter": 243,
      "kind": "api",
      "title": "/leaderboard.csv medal-leaderboard download — columns match /api/public/leaderboard JSON (rank, slug/id, names, discipline, gold/silver/bronze/total/weight). Optional ?year=YYYY. Filename becomes ofc-leaderboard-YYYY.csv when year is set. Registered in feeds.json + /developers. Verified header-only CSV against empty results store",
      "raw": "2026-04-23 · iter 243 · api · /leaderboard.csv medal-leaderboard download — columns match /api/public/leaderboard JSON (rank, slug/id, names, discipline, gold/silver/bronze/total/weight). Optional ?year=YYYY. Filename becomes ofc-leaderboard-YYYY.csv when year is set. Registered in feeds.json + /developers. Verified header-only CSV against empty results store"
    },
    {
      "date": "2026-04-23",
      "iter": 242,
      "kind": "maintenance",
      "title": "smoke.sh 79->85 endpoints covering iter 237 (/api/public/venues), iter 239 (/api/public/venues/oman-club), iter 240 (/api/public/gallery + ?limit=2), iter 241 (/api/public/leaderboard + ?year=2025 + ?limit=5). 85/85 green on prod (first run had transient 502s on athletes.csv/events.csv, cleared on retry)",
      "raw": "2026-04-23 · iter 242 · maintenance · smoke.sh 79->85 endpoints covering iter 237 (/api/public/venues), iter 239 (/api/public/venues/oman-club), iter 240 (/api/public/gallery + ?limit=2), iter 241 (/api/public/leaderboard + ?year=2025 + ?limit=5). 85/85 green on prod (first run had transient 502s on athletes.csv/events.csv, cleared on retry)"
    },
    {
      "date": "2026-04-23",
      "iter": 241,
      "kind": "api",
      "title": "/api/public/leaderboard medal aggregate — uses lib/results.medalLeaderboard (weighted 3/2/1), joins with athlete names/slug, optional ?year=YYYY date-prefix + ?limit=1-500 (default 200). Registered in feeds/openapi/developers. 200 count=0 (results store empty) behaves correctly",
      "raw": "2026-04-23 · iter 241 · api · /api/public/leaderboard medal aggregate — uses lib/results.medalLeaderboard (weighted 3/2/1), joins with athlete names/slug, optional ?year=YYYY date-prefix + ?limit=1-500 (default 200). Registered in feeds/openapi/developers. 200 count=0 (results store empty) behaves correctly"
    },
    {
      "date": "2026-04-23",
      "iter": 240,
      "kind": "api",
      "title": "/api/public/gallery JSON endpoint — ordered by display order, src rewritten to absolute https://fencing.om URLs, ?limit=1-500 (default 200). Registered in feeds.json + openapi + /developers. Verified count=2 with ?limit=2",
      "raw": "2026-04-23 · iter 240 · api · /api/public/gallery JSON endpoint — ordered by display order, src rewritten to absolute https://fencing.om URLs, ?limit=1-500 (default 200). Registered in feeds.json + openapi + /developers. Verified count=2 with ?limit=2"
    },
    {
      "date": "2026-04-23",
      "iter": 239,
      "kind": "api",
      "title": "/api/public/venues/{slug} single-venue detail + venueSlug()+findVenueBySlug() helpers in lib/venues.ts; list endpoint now includes slug field so consumers can link to detail; openapi + /developers updated. Verified oman-club=200, masirah-club=200, no-such=404",
      "raw": "2026-04-23 · iter 239 · api · /api/public/venues/{slug} single-venue detail + venueSlug()+findVenueBySlug() helpers in lib/venues.ts; list endpoint now includes slug field so consumers can link to detail; openapi + /developers updated. Verified oman-club=200, masirah-club=200, no-such=404"
    },
    {
      "date": "2026-04-23",
      "iter": 238,
      "kind": "maintenance",
      "title": "/venues.csv download (shares lib/venues.ts with /venues + /api/public/venues; 9 columns incl derived mapUrl) + smoke.sh extended to 79 endpoints covering iter 232 (/athletes/{slug}/results.csv+.json), iter 234 (/api/public/coaches), iter 236 (/api/public/coaches/{id}), iter 237 (/api/public/venues + /venues.csv). 79/79 green on prod",
      "raw": "2026-04-23 · iter 238 · maintenance · /venues.csv download (shares lib/venues.ts with /venues + /api/public/venues; 9 columns incl derived mapUrl) + smoke.sh extended to 79 endpoints covering iter 232 (/athletes/{slug}/results.csv+.json), iter 234 (/api/public/coaches), iter 236 (/api/public/coaches/{id}), iter 237 (/api/public/venues + /venues.csv). 79/79 green on prod"
    },
    {
      "date": "2026-04-23",
      "iter": 237,
      "kind": "api",
      "title": "/api/public/venues JSON endpoint — extracted VENUES from venues.astro into src/lib/venues.ts (shared source of truth); exposes 8 training venues with clubEn/Ar, city, address, parking, mapQ + derived mapUrl. Registered in /api/public index, feeds.json, openapi.json, /developers. HTML /venues still green",
      "raw": "2026-04-23 · iter 237 · api · /api/public/venues JSON endpoint — extracted VENUES from venues.astro into src/lib/venues.ts (shared source of truth); exposes 8 training venues with clubEn/Ar, city, address, parking, mapQ + derived mapUrl. Registered in /api/public index, feeds.json, openapi.json, /developers. HTML /venues still green"
    },
    {
      "date": "2026-04-23",
      "iter": 236,
      "kind": "api",
      "title": "/api/public/coaches/{id} single-coach detail (parity with committee/{id} + clubs/{slug}); returns 404 when the id exists but is not a coach (role /coach|مدرب/). Verified tm_hisham_karshod=200, tm_khalid_shuaibi=404, nope-xyz=404. Added to openapi.json + /developers",
      "raw": "2026-04-23 · iter 236 · api · /api/public/coaches/{id} single-coach detail (parity with committee/{id} + clubs/{slug}); returns 404 when the id exists but is not a coach (role /coach|مدرب/). Verified tm_hisham_karshod=200, tm_khalid_shuaibi=404, nope-xyz=404. Added to openapi.json + /developers"
    },
    {
      "date": "2026-04-23",
      "iter": 235,
      "kind": "api",
      "title": "register iter-234 /api/public/coaches in 4 registry surfaces — /api/public index (endpoints.coaches), /api/public/feeds.json (added alongside coaches.csv), /api/openapi.json (path entry), /developers (table row); verified all 4 live",
      "raw": "2026-04-23 · iter 235 · api · register iter-234 /api/public/coaches in 4 registry surfaces — /api/public index (endpoints.coaches), /api/public/feeds.json (added alongside coaches.csv), /api/openapi.json (path entry), /developers (table row); verified all 4 live"
    },
    {
      "date": "2026-04-23",
      "iter": 234,
      "kind": "api",
      "title": "/api/public/coaches JSON mirrors the existing coaches.csv — filters team by /coach|مدرب/i, strips email/phone, exposes id/nameEn+Ar/roleEn+Ar/photo/bio/certificationLevel/joinedAt. Verified 200 count=1 (Hisham Karshod)",
      "raw": "2026-04-23 · iter 234 · api · /api/public/coaches JSON mirrors the existing coaches.csv — filters team by /coach|مدرب/i, strips email/phone, exposes id/nameEn+Ar/roleEn+Ar/photo/bio/certificationLevel/joinedAt. Verified 200 count=1 (Hisham Karshod)"
    },
    {
      "date": "2026-04-23",
      "iter": 233,
      "kind": "ux",
      "title": "surface iter-232 per-athlete Results CSV + JSON downloads as ghost-buttons next to Passport PDF on /athletes/{slug} (bilingual labels, fa-table + fa-brackets-curly icons) — verified both links render on jana-al-sharji prod",
      "raw": "2026-04-23 · iter 233 · ux · surface iter-232 per-athlete Results CSV + JSON downloads as ghost-buttons next to Passport PDF on /athletes/{slug} (bilingual labels, fa-table + fa-brackets-curly icons) — verified both links render on jana-al-sharji prod"
    },
    {
      "date": "2026-04-23",
      "iter": 232,
      "kind": "api",
      "title": "/athletes/{slug}/results.csv + results.json per-athlete match-history endpoints (item #24 missing-features); slug→athleteId via readAthletes + resultsForAthlete + medalTally; unknown slug 404, empty roster returns header-only CSV / count=0 JSON",
      "raw": "2026-04-23 · iter 232 · api · /athletes/{slug}/results.csv + results.json per-athlete match-history endpoints (item #24 missing-features); slug→athleteId via readAthletes + resultsForAthlete + medalTally; unknown slug 404, empty roster returns header-only CSV / count=0 JSON"
    },
    {
      "date": "2026-04-23",
      "iter": 231,
      "kind": "ux",
      "title": "global '/' keybind focuses first search input on any public page (GitHub/Notion style, skips when typing in input/textarea/contenteditable); focuses+selects",
      "raw": "2026-04-23 · iter 231 · ux · global '/' keybind focuses first search input on any public page (GitHub/Notion style, skips when typing in input/textarea/contenteditable); focuses+selects"
    },
    {
      "date": "2026-04-23",
      "iter": 230,
      "kind": "maintenance",
      "title": "smoke.sh extended with 12 filter permutations from iters 225-229 (weapon, club, year, tag, q, limit, medal, types=) — 61→73 endpoints, 73/73 green against prod",
      "raw": "2026-04-23 · iter 230 · maintenance · smoke.sh extended with 12 filter permutations from iters 225-229 (weapon, club, year, tag, q, limit, medal, types=) — 61→73 endpoints, 73/73 green against prod"
    },
    {
      "date": "2026-04-23",
      "iter": 229,
      "kind": "api",
      "title": "/api/public/results ?athleteSlug (or athleteId) + ?medal + ?year + ?weapon + ?limit (1-500, default 200) filters; store currently empty so all permutations return 200 count=0",
      "raw": "2026-04-23 · iter 229 · api · /api/public/results ?athleteSlug (or athleteId) + ?medal + ?year + ?weapon + ?limit (1-500, default 200) filters; store currently empty so all permutations return 200 count=0"
    },
    {
      "date": "2026-04-23",
      "iter": 228,
      "kind": "sync",
      "title": "refresh content/loop-log.txt from 197 to 227 — footer 'Built in public · N iterations shipped' + /changelog + /changelog.rss.xml + /changelog.atom.xml all bump in one ship",
      "raw": "2026-04-23 · iter 228 · sync · refresh content/loop-log.txt from 197 to 227 — footer 'Built in public · N iterations shipped' + /changelog + /changelog.rss.xml + /changelog.atom.xml all bump in one ship"
    },
    {
      "date": "2026-04-23",
      "iter": 227,
      "kind": "api",
      "title": "/api/public/news ?year= date-prefix + ?q= NFD-normalised substring match + ?limit=1-200 (default 50). Verified 22 total → 5 from 2025 → 1 matching 'bahrain' → 3 capped by limit",
      "raw": "2026-04-23 · iter 227 · api · /api/public/news ?year= date-prefix + ?q= NFD-normalised substring match + ?limit=1-200 (default 50). Verified 22 total → 5 from 2025 → 1 matching 'bahrain' → 3 capped by limit"
    },
    {
      "date": "2026-04-23",
      "iter": 226,
      "kind": "api",
      "title": "/api/public/events ?year=YYYY date-prefix match + ?tag= case-insensitive exact match; verified 8 total → 3 from 2025, 2 international, 1 composed",
      "raw": "2026-04-23 · iter 226 · api · /api/public/events ?year=YYYY date-prefix match + ?tag= case-insensitive exact match; verified 8 total → 3 from 2025, 2 international, 1 composed"
    },
    {
      "date": "2026-04-23",
      "iter": 225,
      "kind": "api",
      "title": "/api/public/athletes ?weapon=foil|epee|sabre (NFD) + ?club=substring + ?active=0 filters; exposes discipline+gender on each row. Verified 13 total → 7 foil + 6 epee, 3 Salalah by club",
      "raw": "2026-04-23 · iter 225 · api · /api/public/athletes ?weapon=foil|epee|sabre (NFD) + ?club=substring + ?active=0 filters; exposes discipline+gender on each row. Verified 13 total → 7 foil + 6 epee, 3 Salalah by club"
    },
    {
      "date": "2026-04-23",
      "iter": 224,
      "kind": "ux",
      "title": "richer ShareButton titles on athlete/event/news/club detail pages (athlete: name+category+discipline+club; event: title+date+tag; news: title+date; club: name+city — all suffixed 'Oman Fencing')",
      "raw": "2026-04-23 · iter 224 · ux · richer ShareButton titles on athlete/event/news/club detail pages (athlete: name+category+discipline+club; event: title+date+tag; news: title+date; club: name+city — all suffixed 'Oman Fencing')"
    },
    {
      "date": "2026-04-23",
      "iter": 223,
      "kind": "api",
      "title": "/api/public/search/suggest?types= mirrors iter-222 filter on the typeahead endpoint — verified q=al types=athlete returns 8 athletes vs 4 types mixed unfiltered",
      "raw": "2026-04-23 · iter 223 · api · /api/public/search/suggest?types= mirrors iter-222 filter on the typeahead endpoint — verified q=al types=athlete returns 8 athletes vs 4 types mixed unfiltered"
    },
    {
      "date": "2026-04-23",
      "iter": 222,
      "kind": "api",
      "title": "/api/public/search?types=a,b,c filter (comma-separated allowlist of athlete/event/news/club/committee; unknown vals dropped; empty keeps all) — verified q=jana types=athlete narrows from 5 mixed to 1 athlete",
      "raw": "2026-04-23 · iter 222 · api · /api/public/search?types=a,b,c filter (comma-separated allowlist of athlete/event/news/club/committee; unknown vals dropped; empty keeps all) — verified q=jana types=athlete narrows from 5 mixed to 1 athlete"
    },
    {
      "date": "2026-04-23",
      "iter": 221,
      "kind": "discoverability",
      "title": "/.well-known/change-password 302 to /login for W3C password-manager discovery (Bitwarden, Apple Passwords, 1Password, Chrome leaked-password flow); fencing.om is OTP so redirect to re-authentication page",
      "raw": "2026-04-23 · iter 221 · discoverability · /.well-known/change-password 302 to /login for W3C password-manager discovery (Bitwarden, Apple Passwords, 1Password, Chrome leaked-password flow); fencing.om is OTP so redirect to re-authentication page"
    },
    {
      "date": "2026-04-23",
      "iter": 220,
      "kind": "maintenance",
      "title": "scripts/smoke.sh 61-endpoint smoke suite (discovery/feeds/registries/entities/aggregates/search/detail/pages, HTTP code + content-type substring check, exits non-zero on any mismatch, 61/61 green against prod now)",
      "raw": "2026-04-23 · iter 220 · maintenance · scripts/smoke.sh 61-endpoint smoke suite (discovery/feeds/registries/entities/aggregates/search/detail/pages, HTTP code + content-type substring check, exits non-zero on any mismatch, 61/61 green against prod now)"
    },
    {
      "date": "2026-04-23",
      "iter": 219,
      "kind": "refactor",
      "title": "DRY spotlight picker into src/lib/spotlight.ts todaySpotlight(); /api/public/spotlight + SpotlightFeature both delegate — guaranteed same pick (API and homepage both return Salma Al Daghishi today); -35 net lines",
      "raw": "2026-04-23 · iter 219 · refactor · DRY spotlight picker into src/lib/spotlight.ts todaySpotlight(); /api/public/spotlight + SpotlightFeature both delegate — guaranteed same pick (API and homepage both return Salma Al Daghishi today); -35 net lines"
    },
    {
      "date": "2026-04-23",
      "iter": 218,
      "kind": "ux",
      "title": "SpotlightFeature on homepage surfaces iter-217 athlete-of-day (same FNV-1a hash ensures page pick matches API pick; renders between Stats and About with photo + discipline badge + category + club)",
      "raw": "2026-04-23 · iter 218 · ux · SpotlightFeature on homepage surfaces iter-217 athlete-of-day (same FNV-1a hash ensures page pick matches API pick; renders between Stats and About with photo + discipline badge + category + club)"
    },
    {
      "date": "2026-04-23",
      "iter": 217,
      "kind": "api",
      "title": "/api/public/spotlight athlete-of-the-day (FNV-1a(yyyy-mm-dd) mod active-roster, same pick all day, rotates at UTC midnight) — today's pick: Salma Al Daghishi",
      "raw": "2026-04-23 · iter 217 · api · /api/public/spotlight athlete-of-the-day (FNV-1a(yyyy-mm-dd) mod active-roster, same pick all day, rotates at UTC midnight) — today's pick: Salma Al Daghishi"
    },
    {
      "date": "2026-04-23",
      "iter": 216,
      "kind": "discoverability",
      "title": "footer bottom bar surfaces /now (iter 206) + /developers (iter 170) alongside Privacy/Terms/CoC/Security",
      "raw": "2026-04-23 · iter 216 · discoverability · footer bottom bar surfaces /now (iter 206) + /developers (iter 170) alongside Privacy/Terms/CoC/Security"
    },
    {
      "date": "2026-04-23",
      "iter": 215,
      "kind": "ux",
      "title": "'Jump to' anchor pills on /committee (President + board-members grid anchor + CSV download, star/users/file-csv icons, EN+AR)",
      "raw": "2026-04-23 · iter 215 · ux · 'Jump to' anchor pills on /committee (President + board-members grid anchor + CSV download, star/users/file-csv icons, EN+AR)"
    },
    {
      "date": "2026-04-23",
      "iter": 214,
      "kind": "ux",
      "title": "client-side filter on /clubs index (name+city search, NFD-normalised, shown/total counter); mirrors iter-213 committee filter",
      "raw": "2026-04-23 · iter 214 · ux · client-side filter on /clubs index (name+city search, NFD-normalised, shown/total counter); mirrors iter-213 committee filter"
    },
    {
      "date": "2026-04-23",
      "iter": 213,
      "kind": "ux",
      "title": "client-side filter input above /committee board grid (NFD-normalised, live shown/total count pill, empty-state msg, no network roundtrip)",
      "raw": "2026-04-23 · iter 213 · ux · client-side filter input above /committee board grid (NFD-normalised, live shown/total count pill, empty-state msg, no network roundtrip)"
    },
    {
      "date": "2026-04-23",
      "iter": 212,
      "kind": "export",
      "title": "/coaches.csv (head coach + certified coaches filtered by coach|مدرب role pattern, NO email/phone, 600s cache) registered in feeds.json catalog and /developers docs",
      "raw": "2026-04-23 · iter 212 · export · /coaches.csv (head coach + certified coaches filtered by coach|مدرب role pattern, NO email/phone, 600s cache) registered in feeds.json catalog and /developers docs"
    },
    {
      "date": "2026-04-23",
      "iter": 211,
      "kind": "seo",
      "title": "/sitemap-images.xml Google image sitemap (event photos + news covers + athlete photos grouped by page URL with image:title context, schema 1.1, 1h cache); listed in sitemap.xml index",
      "raw": "2026-04-23 · iter 211 · seo · /sitemap-images.xml Google image sitemap (event photos + news covers + athlete photos grouped by page URL with image:title context, schema 1.1, 1h cache); listed in sitemap.xml index"
    },
    {
      "date": "2026-04-23",
      "iter": 210,
      "kind": "ux",
      "title": "/committee member cards get id={member.id} + scroll-mt-24 so /committee#tm_president deep-links work; matches iter-201 /api/public/committee/{id} URL contract",
      "raw": "2026-04-23 · iter 210 · ux · /committee member cards get id={member.id} + scroll-mt-24 so /committee#tm_president deep-links work; matches iter-201 /api/public/committee/{id} URL contract"
    },
    {
      "date": "2026-04-23",
      "iter": 209,
      "kind": "api",
      "title": "/api/public/sitemap.json (100 static pages + 51 dynamic news/events/athletes/clubs URLs deduped+sorted, 150 total, each with relative path + absolute URL, 600s cache)",
      "raw": "2026-04-23 · iter 209 · api · /api/public/sitemap.json (100 static pages + 51 dynamic news/events/athletes/clubs URLs deduped+sorted, 150 total, each with relative path + absolute URL, 600s cache)"
    },
    {
      "date": "2026-04-23",
      "iter": 208,
      "kind": "ux",
      "title": "/now page gets JSON snapshot + .ics subscribe + News RSS pill row (mirrors iter-178 news index pattern, surfaces structured siblings)",
      "raw": "2026-04-23 · iter 208 · ux · /now page gets JSON snapshot + .ics subscribe + News RSS pill row (mirrors iter-178 news index pattern, surfaces structured siblings)"
    },
    {
      "date": "2026-04-23",
      "iter": 207,
      "kind": "api",
      "title": "/api/public/now JSON sibling to /now page (eventsThisWeek + upcomingNext5 with inDays + recentNews 7d + recentResults 30d, 300s cache, registered in /api/public)",
      "raw": "2026-04-23 · iter 207 · api · /api/public/now JSON sibling to /now page (eventsThisWeek + upcomingNext5 with inDays + recentNews 7d + recentResults 30d, 300s cache, registered in /api/public)"
    },
    {
      "date": "2026-04-23",
      "iter": 206,
      "kind": "ux",
      "title": "/now 'what's on this week' snapshot page (4 sections: This Week events / Recent News 7d / Coming Up Next 5 + day count / Recent Results 30d; empty-state pills with deep links; sitemap daily-changefreq)",
      "raw": "2026-04-23 · iter 206 · ux · /now 'what's on this week' snapshot page (4 sections: This Week events / Recent News 7d / Coming Up Next 5 + day count / Recent Results 30d; empty-state pills with deep links; sitemap daily-changefreq)"
    },
    {
      "date": "2026-04-23",
      "iter": 205,
      "kind": "ux",
      "title": "NewsFeature on homepage (3 latest news cards inserted after Events before Gallery, mirrors /news index card style with date badge + line-clamp-2 title + RTL-aware arrow, 'All news' link in header)",
      "raw": "2026-04-23 · iter 205 · ux · NewsFeature on homepage (3 latest news cards inserted after Events before Gallery, mirrors /news index card style with date badge + line-clamp-2 title + RTL-aware arrow, 'All news' link in header)"
    },
    {
      "date": "2026-04-23",
      "iter": 204,
      "kind": "docs",
      "title": "/developers refreshed with all endpoints shipped since iter 170 (+committee/{id}, +disciplines, +changelog, +feeds.json, +new Search group with full+suggest, +atom feeds, +per-event ICS, +per-club CSV/JSON, +openapi.json)",
      "raw": "2026-04-23 · iter 204 · docs · /developers refreshed with all endpoints shipped since iter 170 (+committee/{id}, +disciplines, +changelog, +feeds.json, +new Search group with full+suggest, +atom feeds, +per-event ICS, +per-club CSV/JSON, +openapi.json)"
    },
    {
      "date": "2026-04-23",
      "iter": 203,
      "kind": "api",
      "title": "/api/openapi.json refreshed to v1.1.0 (15→21 paths) covering committee/{id} disciplines changelog feeds.json search search/suggest shipped since iter 171",
      "raw": "2026-04-23 · iter 203 · api · /api/openapi.json refreshed to v1.1.0 (15→21 paths) covering committee/{id} disciplines changelog feeds.json search search/suggest shipped since iter 171"
    },
    {
      "date": "2026-04-23",
      "iter": 202,
      "kind": "api",
      "title": "/api/public/search/suggest?q= typeahead endpoint (max 8 title-only results, 30s cache, NFD scoring); search.ts moved to search/index.ts to allow sibling; registered in /api/public",
      "raw": "2026-04-23 · iter 202 · api · /api/public/search/suggest?q= typeahead endpoint (max 8 title-only results, 30s cache, NFD scoring); search.ts moved to search/index.ts to allow sibling; registered in /api/public"
    },
    {
      "date": "2026-04-23",
      "iter": 201,
      "kind": "api",
      "title": "/api/public/committee/[id] per-member JSON (President's record returns full safe-fields, unknown 404, list endpoint moved committee.ts → committee/index.ts to allow [id].ts sibling); per-entity detail set complete across athletes (166), events (167), news+clubs (168), committee (201)",
      "raw": "2026-04-23 · iter 201 · api · /api/public/committee/[id] per-member JSON (President's record returns full safe-fields, unknown 404, list endpoint moved committee.ts → committee/index.ts to allow [id].ts sibling); per-entity detail set complete across athletes (166), events (167), news+clubs (168), committee (201)"
    },
    {
      "date": "2026-04-23",
      "iter": 200,
      "kind": "MILESTONE",
      "title": "/api/public/search?q=&limit=N cross-entity search (athletes+news+events+clubs+committee, NFD-normalised exact>prefix>substring scoring, news falls back to body scan, snippet+score per hit, 60s cache, registered in /api/public)",
      "raw": "2026-04-23 · iter 200 · MILESTONE · /api/public/search?q=&limit=N cross-entity search (athletes+news+events+clubs+committee, NFD-normalised exact>prefix>substring scoring, news falls back to body scan, snippet+score per hit, 60s cache, registered in /api/public)"
    },
    {
      "date": "2026-04-23",
      "iter": 199,
      "kind": "a11y",
      "title": "aria-current='page' + green tint on active Nav item (top + mobile drawer); active when pathname === target OR startsWith target+/, so /news/<slug> still highlights News",
      "raw": "2026-04-23 · iter 199 · a11y · aria-current='page' + green tint on active Nav item (top + mobile drawer); active when pathname === target OR startsWith target+/, so /news/<slug> still highlights News"
    },
    {
      "date": "2026-04-23",
      "iter": 198,
      "kind": "ux",
      "title": "'Built in public · N iterations shipped' centered footer line on every public page (reads max iter from content/loop-log.txt at module load, links to /changelog, EN+AR; +log refresh to keep number current)",
      "raw": "2026-04-23 · iter 198 · ux · 'Built in public · N iterations shipped' centered footer line on every public page (reads max iter from content/loop-log.txt at module load, links to /changelog, EN+AR; +log refresh to keep number current)"
    },
    {
      "date": "2026-04-23",
      "iter": 197,
      "kind": "feed",
      "title": "/changelog.atom.xml Atom 1.0 sibling to iter-188 RSS (100 entries, autodiscovered on /changelog via head slot, entry in /api/public/feeds.json catalog)",
      "raw": "2026-04-23 · iter 197 · feed · /changelog.atom.xml Atom 1.0 sibling to iter-188 RSS (100 entries, autodiscovered on /changelog via head slot, entry in /api/public/feeds.json catalog)"
    },
    {
      "date": "2026-04-23",
      "iter": 196,
      "kind": "api",
      "title": "/api/public/feeds.json catalog (24 feeds across 8 MIME types: rss/atom/json/xml/calendar/csv/plain/opensearch) — entity tag + format + description + absoluteUrl per entry, 1h cache, registered in /api/public",
      "raw": "2026-04-23 · iter 196 · api · /api/public/feeds.json catalog (24 feeds across 8 MIME types: rss/atom/json/xml/calendar/csv/plain/opensearch) — entity tag + format + description + absoluteUrl per entry, 1h cache, registered in /api/public"
    },
    {
      "date": "2026-04-23",
      "iter": 195,
      "kind": "feed",
      "title": "/news.atom.xml Atom 1.0 sibling to RSS (per-entry xml:lang, proper id/published/updated, author block, 600s cache) + Base.astro adds <link rel=alternate type=application/atom+xml> alongside the RSS link",
      "raw": "2026-04-23 · iter 195 · feed · /news.atom.xml Atom 1.0 sibling to RSS (per-entry xml:lang, proper id/published/updated, author block, 600s cache) + Base.astro adds <link rel=alternate type=application/atom+xml> alongside the RSS link"
    },
    {
      "date": "2026-04-23",
      "iter": 194,
      "kind": "ux",
      "title": "reading-time badge on /news index cards (precomputed in frontmatter from readNewsArticle, 200wpm EN/180wpm AR, EN 'X min'/AR 'X د' next to date)",
      "raw": "2026-04-23 · iter 194 · ux · reading-time badge on /news index cards (precomputed in frontmatter from readNewsArticle, 200wpm EN/180wpm AR, EN 'X min'/AR 'X د' next to date)"
    },
    {
      "date": "2026-04-23",
      "iter": 192,
      "kind": "ux+fix",
      "title": "born/age badge on athlete profile when birthYear present (3 athletes today: israa-al-siyabi, ahmed-keskes, ali-al-busaidi); iter 192 IIFE pattern silently dropped by Astro template parser, iter 193 fixed by computing ageEoY in frontmatter and using plain {athlete.birthYear && (...)} expression",
      "raw": "2026-04-23 · iter 192-193 · ux+fix · born/age badge on athlete profile when birthYear present (3 athletes today: israa-al-siyabi, ahmed-keskes, ali-al-busaidi); iter 192 IIFE pattern silently dropped by Astro template parser, iter 193 fixed by computing ageEoY in frontmatter and using plain {athlete.birthYear && (...)} expression"
    },
    {
      "date": "2026-04-23",
      "iter": 191,
      "kind": "api",
      "title": "/clubs/<slug>/athletes.json sibling to iter-190 per-club CSV (same tolerant club-name match, returns club identity + roster array, 404 on unknown, 600s cache)",
      "raw": "2026-04-23 · iter 191 · api · /clubs/<slug>/athletes.json sibling to iter-190 per-club CSV (same tolerant club-name match, returns club identity + roster array, 404 on unknown, 600s cache)"
    },
    {
      "date": "2026-04-23",
      "iter": 190,
      "kind": "export",
      "title": "/clubs/<slug>/athletes.csv per-club roster CSV (tolerant club-name match handles 'Quriyat'/'Quryat' transliteration variants, NFD-normalised, strips 'club' word) + Roster CSV card surfaced on /clubs/[slug] alongside venue + how-to-join",
      "raw": "2026-04-23 · iter 190 · export · /clubs/<slug>/athletes.csv per-club roster CSV (tolerant club-name match handles 'Quriyat'/'Quryat' transliteration variants, NFD-normalised, strips 'club' word) + Roster CSV card surfaced on /clubs/[slug] alongside venue + how-to-join"
    },
    {
      "date": "2026-04-23",
      "iter": 189,
      "kind": "seo",
      "title": "Base.astro gains <slot name='head'/> + /changelog adds <link rel=alternate rss> for iter-188 changelog.rss.xml so feed readers auto-detect it",
      "raw": "2026-04-23 · iter 189 · seo · Base.astro gains <slot name='head'/> + /changelog adds <link rel=alternate rss> for iter-188 changelog.rss.xml so feed readers auto-detect it"
    },
    {
      "date": "2026-04-23",
      "iter": 188,
      "kind": "feed",
      "title": "/changelog.rss.xml RSS 2.0 of last 100 shipped iterations (parses content/loop-log.txt) + RSS+JSON CTA pills under /changelog intro (mirrors iter 178 news pattern)",
      "raw": "2026-04-23 · iter 188 · feed · /changelog.rss.xml RSS 2.0 of last 100 shipped iterations (parses content/loop-log.txt) + RSS+JSON CTA pills under /changelog intro (mirrors iter 178 news pattern)"
    },
    {
      "date": "2026-04-23",
      "iter": 187,
      "kind": "api+sync",
      "title": "refresh in-repo content/loop-log.txt (was 1 day stale, now reflects iter 186) + new /api/public/changelog JSON feed parsing same file (?limit=N, max 500) registered in /api/public; 175 entries indexed",
      "raw": "2026-04-23 · iter 187 · api+sync · refresh in-repo content/loop-log.txt (was 1 day stale, now reflects iter 186) + new /api/public/changelog JSON feed parsing same file (?limit=N, max 500) registered in /api/public; 175 entries indexed"
    },
    {
      "date": "2026-04-23",
      "iter": 186,
      "kind": "api",
      "title": "/api/public/disciplines reference endpoint (3 weapons foil/épée/sabre with EN+AR names, target area, scoring rules, pageUrl) registered in /api/public, 24h cache",
      "raw": "2026-04-23 · iter 186 · api · /api/public/disciplines reference endpoint (3 weapons foil/épée/sabre with EN+AR names, target area, scoring rules, pageUrl) registered in /api/public, 24h cache"
    },
    {
      "date": "2026-04-23",
      "iter": 185,
      "kind": "ux",
      "title": "ShareButton on /clubs/[slug] completes detail-page set (news + events + athletes + clubs all have prev/next nav AND share button)",
      "raw": "2026-04-23 · iter 185 · ux · ShareButton on /clubs/[slug] completes detail-page set (news + events + athletes + clubs all have prev/next nav AND share button)"
    },
    {
      "date": "2026-04-23",
      "iter": 184,
      "kind": "refactor+ux",
      "title": "extract ShareButton.astro reusable component (data-attr + idempotent document-level handler binding via data-bound flag); /news/[slug] + /events/[slug] swap to component (-80 lines); /athletes/[slug] gains share button above prev/next nav",
      "raw": "2026-04-23 · iter 184 · refactor+ux · extract ShareButton.astro reusable component (data-attr + idempotent document-level handler binding via data-bound flag); /news/[slug] + /events/[slug] swap to component (-80 lines); /athletes/[slug] gains share button above prev/next nav"
    },
    {
      "date": "2026-04-23",
      "iter": 183,
      "kind": "ux",
      "title": "share button on /events/[slug] mirrors iter-182 news (navigator.share native + clipboard fallback + transient flash, EN+AR)",
      "raw": "2026-04-23 · iter 183 · ux · share button on /events/[slug] mirrors iter-182 news (navigator.share native + clipboard fallback + transient flash, EN+AR)"
    },
    {
      "date": "2026-04-23",
      "iter": 182,
      "kind": "ux",
      "title": "share button on /news/[slug] (navigator.share native dialog on mobile/PWA, navigator.clipboard fallback with 'Link copied'/'تم النسخ' transient flash, EN+AR)",
      "raw": "2026-04-23 · iter 182 · ux · share button on /news/[slug] (navigator.share native dialog on mobile/PWA, navigator.clipboard fallback with 'Link copied'/'تم النسخ' transient flash, EN+AR)"
    },
    {
      "date": "2026-04-23",
      "iter": 181,
      "kind": "ux",
      "title": "reading-time estimate badge in /news/[slug] meta strip ('X min read' EN @200wpm + 'قراءة X دقيقة' AR @180wpm; hidden when body empty; AR badge uses flex-ar helper for proper display:flex on lang=ar)",
      "raw": "2026-04-23 · iter 181 · ux · reading-time estimate badge in /news/[slug] meta strip ('X min read' EN @200wpm + 'قراءة X دقيقة' AR @180wpm; hidden when body empty; AR badge uses flex-ar helper for proper display:flex on lang=ar)"
    },
    {
      "date": "2026-04-23",
      "iter": 180,
      "kind": "ux",
      "title": "/clubs index gets CSV + JSON API + Leaderboard pill row (completes listing-page export pattern across news 178 athletes+events 179 clubs 180)",
      "raw": "2026-04-23 · iter 180 · ux · /clubs index gets CSV + JSON API + Leaderboard pill row (completes listing-page export pattern across news 178 athletes+events 179 clubs 180)"
    },
    {
      "date": "2026-04-23",
      "iter": 179,
      "kind": "ux",
      "title": "/athletes + /events index pages get JSON API export pill next to existing CSV/ICS pills (mirrors iter 178 news pattern)",
      "raw": "2026-04-23 · iter 179 · ux · /athletes + /events index pages get JSON API export pill next to existing CSV/ICS pills (mirrors iter 178 news pattern)"
    },
    {
      "date": "2026-04-23",
      "iter": 178,
      "kind": "ux",
      "title": "/news index gets RSS + Email + JSON API CTA strip surfacing /news.rss.xml + /#newsletter + /api/public/news (3 pills below search row, EN+AR, hover green-ofc)",
      "raw": "2026-04-23 · iter 178 · ux · /news index gets RSS + Email + JSON API CTA strip surfacing /news.rss.xml + /#newsletter + /api/public/news (3 pills below search row, EN+AR, hover green-ofc)"
    },
    {
      "date": "2026-04-23",
      "iter": 177,
      "kind": "ux",
      "title": "'Add to calendar' button on /events/[slug] surfaces iter-176 per-event ICS endpoint (calendar-plus icon, download attribute, EN+AR labels, leads the CTA strip above 'Full medal table' and 'All athletes')",
      "raw": "2026-04-23 · iter 177 · ux · 'Add to calendar' button on /events/[slug] surfaces iter-176 per-event ICS endpoint (calendar-plus icon, download attribute, EN+AR labels, leads the CTA strip above 'Full medal table' and 'All athletes')"
    },
    {
      "date": "2026-04-23",
      "iter": 176,
      "kind": "feed",
      "title": "/events/<id>.ics per-event iCalendar export (RFC 5545 line-folding+escape, X-WR-CALNAME=event title, 600s cache, CORS *) sibling to /events.ics whole-calendar feed",
      "raw": "2026-04-23 · iter 176 · feed · /events/<id>.ics per-event iCalendar export (RFC 5545 line-folding+escape, X-WR-CALNAME=event title, 600s cache, CORS *) sibling to /events.ics whole-calendar feed"
    },
    {
      "date": "2026-04-23",
      "iter": 175,
      "kind": "ux",
      "title": "prev/next nav on /clubs/[slug] (alphabetical EN sort) — completes detail-page nav set across news (172) events (173) athletes (174) clubs (175)",
      "raw": "2026-04-23 · iter 175 · ux · prev/next nav on /clubs/[slug] (alphabetical EN sort) — completes detail-page nav set across news (172) events (173) athletes (174) clubs (175)"
    },
    {
      "date": "2026-04-23",
      "iter": 174,
      "kind": "ux",
      "title": "prev/next nav on /athletes/[slug] completes the trio (news iter 172 + events iter 173 + athletes iter 174); active roster sorted alphabetically by EN name, edge entries single card, RTL-aware",
      "raw": "2026-04-23 · iter 174 · ux · prev/next nav on /athletes/[slug] completes the trio (news iter 172 + events iter 173 + athletes iter 174); active roster sorted alphabetically by EN name, edge entries single card, RTL-aware"
    },
    {
      "date": "2026-04-23",
      "iter": 173,
      "kind": "ux",
      "title": "prev/next event navigation on /events/[slug] mirrors iter 172 news pattern (sorted by date newest-first, RTL-aware arrows, line-clamp-2 titles, edge events single card)",
      "raw": "2026-04-23 · iter 173 · ux · prev/next event navigation on /events/[slug] mirrors iter 172 news pattern (sorted by date newest-first, RTL-aware arrows, line-clamp-2 titles, edge events single card)"
    },
    {
      "date": "2026-04-23",
      "iter": 172,
      "kind": "ux",
      "title": "prev/next article navigation on /news/[slug] — grid 2-col older+newer cards, RTL-aware arrows, line-clamp-2 titles, hover green-ofc border; edge articles show single card",
      "raw": "2026-04-23 · iter 172 · ux · prev/next article navigation on /news/[slug] — grid 2-col older+newer cards, RTL-aware arrows, line-clamp-2 titles, hover green-ofc border; edge articles show single card"
    },
    {
      "date": "2026-04-23",
      "iter": 171,
      "kind": "api",
      "title": "/api/openapi.json OpenAPI 3.1 descriptor (15 paths: entities+aggregates+ops, slug/id path params, envelope schema, Health schema, CC BY 4.0 license, contact info@fencing.om) registered in /api/public index, 1h cache",
      "raw": "2026-04-23 · iter 171 · api · /api/openapi.json OpenAPI 3.1 descriptor (15 paths: entities+aggregates+ops, slug/id path params, envelope schema, Health schema, CC BY 4.0 license, contact info@fencing.om) registered in /api/public index, 1h cache"
    },
    {
      "date": "2026-04-23",
      "iter": 170,
      "kind": "docs",
      "title": "/developers public API + feeds documentation page (bilingual; Registry/Entities/Feeds/Discovery tables; 14 JSON endpoints + 7 CSV/RSS/ICS + 6 discovery files; curl example; cache+CORS notes); added to sitemap-pages.xml",
      "raw": "2026-04-23 · iter 170 · docs · /developers public API + feeds documentation page (bilingual; Registry/Entities/Feeds/Discovery tables; 14 JSON endpoints + 7 CSV/RSS/ICS + 6 discovery files; curl example; cache+CORS notes); added to sitemap-pages.xml"
    },
    {
      "date": "2026-04-23",
      "iter": 169,
      "kind": "export",
      "title": "/committee.csv (id, nameEn, nameAr, roleEn, roleAr, isBoard, joinedAt — NO email/phone; 7 active members) completing CSV export set with athletes/clubs/events/results; text/csv attachment, 600s cache, CORS *",
      "raw": "2026-04-23 · iter 169 · export · /committee.csv (id, nameEn, nameAr, roleEn, roleAr, isBoard, joinedAt — NO email/phone; 7 active members) completing CSV export set with athletes/clubs/events/results; text/csv attachment, 600s cache, CORS *"
    },
    {
      "date": "2026-04-23",
      "iter": 168,
      "kind": "api",
      "title": "/api/public/news/[slug] (slug, titleEn/Ar, date, coverPhoto, bodyEn/Ar, url) + /api/public/clubs/[slug] (slug, names, city, notes, public contact, url) — both 404 on unknown, 600s cache, completing per-entity detail pattern with iters 166 (athletes) and 167 (events)",
      "raw": "2026-04-23 · iter 168 · api · /api/public/news/[slug] (slug, titleEn/Ar, date, coverPhoto, bodyEn/Ar, url) + /api/public/clubs/[slug] (slug, names, city, notes, public contact, url) — both 404 on unknown, 600s cache, completing per-entity detail pattern with iters 166 (athletes) and 167 (events)"
    },
    {
      "date": "2026-04-23",
      "iter": 167,
      "kind": "api",
      "title": "/api/public/events/[id] individual event JSON (id, titleEn/Ar, date, tag/Ar, photo, descEn/Ar, url) — 404 on unknown, 600s cache, for partner embeds and calendar integrations",
      "raw": "2026-04-23 · iter 167 · api · /api/public/events/[id] individual event JSON (id, titleEn/Ar, date, tag/Ar, photo, descEn/Ar, url) — 404 on unknown, 600s cache, for partner embeds and calendar integrations"
    },
    {
      "date": "2026-04-23",
      "iter": 166,
      "kind": "api",
      "title": "/api/public/athletes/[slug] individual athlete JSON (id, slug, nameEn/Ar, discipline, category, gender, club, photo, bioEn/Ar, medals, highlights, FIE fields) — matches by slug OR id, 404 on unknown/inactive, 600s cache, for partner embeds and share-card generators",
      "raw": "2026-04-23 · iter 166 · api · /api/public/athletes/[slug] individual athlete JSON (id, slug, nameEn/Ar, discipline, category, gender, club, photo, bioEn/Ar, medals, highlights, FIE fields) — matches by slug OR id, 404 on unknown/inactive, 600s cache, for partner embeds and share-card generators"
    },
    {
      "date": "2026-04-23",
      "iter": 165,
      "kind": "seo",
      "title": "robots.txt rewritten: old blanket Disallow /api/ hid public JSON feeds from crawlers; now explicit Allow /api/public /api/health /.well-known/ and explicit Disallow /board /my /account /admin /api/admin /api/auth /api/board /api/my /api/ai /api/committee",
      "raw": "2026-04-23 · iter 165 · seo · robots.txt rewritten: old blanket Disallow /api/ hid public JSON feeds from crawlers; now explicit Allow /api/public /api/health /.well-known/ and explicit Disallow /board /my /account /admin /api/admin /api/auth /api/board /api/my /api/ai /api/committee"
    },
    {
      "date": "2026-04-22",
      "iter": 164,
      "kind": "seo",
      "title": "/opensearch.xml OpenSearch 1.1 descriptor (EN + AR, 16/32/192 icons, search template /search?q=) + <link rel='search'> in Base.astro so browsers (Firefox/Chrome/Safari) can add fencing.om as a built-in search engine",
      "raw": "2026-04-22 · iter 164 · seo · /opensearch.xml OpenSearch 1.1 descriptor (EN + AR, 16/32/192 icons, search template /search?q=) + <link rel='search'> in Base.astro so browsers (Firefox/Chrome/Safari) can add fencing.om as a built-in search engine"
    },
    {
      "date": "2026-04-22",
      "iter": 163,
      "kind": "api",
      "title": "/api/public/latest combined one-call feed (5 latest events + 5 latest news + 10 latest results) 300s cache, so widgets don't chain 3 API calls; registered in /api/public index",
      "raw": "2026-04-22 · iter 163 · api · /api/public/latest combined one-call feed (5 latest events + 5 latest news + 10 latest results) 300s cache, so widgets don't chain 3 API calls; registered in /api/public index"
    },
    {
      "date": "2026-04-22",
      "iter": 162,
      "kind": "api+seo",
      "title": "/api/public/upcoming (future events only, sorted soonest first, 600s cache, registered in /api/public index) + sitemap-pages dedup /partners and /hall-of-fame were each listed twice",
      "raw": "2026-04-22 · iter 162 · api+seo · /api/public/upcoming (future events only, sorted soonest first, 600s cache, registered in /api/public index) + sitemap-pages dedup /partners and /hall-of-fame were each listed twice"
    },
    {
      "date": "2026-04-22",
      "iter": 161,
      "kind": "api",
      "title": "/api/public/committee (7 active members, id/nameEn/Ar/roleEn/Ar/photo/bioEn/Ar/isBoard/joinedAt sorted by order) — email/phone deliberately omitted, private contact only via rate-limited /api/committee/contact; registered in /api/public index",
      "raw": "2026-04-22 · iter 161 · api · /api/public/committee (7 active members, id/nameEn/Ar/roleEn/Ar/photo/bioEn/Ar/isBoard/joinedAt sorted by order) — email/phone deliberately omitted, private contact only via rate-limited /api/committee/contact; registered in /api/public index"
    },
    {
      "date": "2026-04-22",
      "iter": 160,
      "kind": "seo",
      "title": "sitemap-pages.xml now indexes all 19 /tools/* routes (door-check-qr first-aid floor-captain kit-check penalty-tracker risk-assessment session-plan share-card training-load training-log training-plan weekly-digest added to the existing 7); 20 total tool entries",
      "raw": "2026-04-22 · iter 160 · seo · sitemap-pages.xml now indexes all 19 /tools/* routes (door-check-qr first-aid floor-captain kit-check penalty-tracker risk-assessment session-plan share-card training-load training-log training-plan weekly-digest added to the existing 7); 20 total tool entries"
    },
    {
      "date": "2026-04-22",
      "iter": 159,
      "kind": "fix",
      "title": "/athletes/[slug] second missing import streakForAthlete (same file) — all 13 athlete profile pages now return 200 (slugs that don't exist return 302 to /athletes, by design)",
      "raw": "2026-04-22 · iter 159 · fix · /athletes/[slug] second missing import streakForAthlete (same file) — all 13 athlete profile pages now return 200 (slugs that don't exist return 302 to /athletes, by design)"
    },
    {
      "date": "2026-04-22",
      "iter": 158,
      "kind": "fix",
      "title": "/athletes/[slug] 500 — missing import pctLast30Days from lib/training, caught by module-audit agent",
      "raw": "2026-04-22 · iter 158 · fix · /athletes/[slug] 500 — missing import pctLast30Days from lib/training, caught by module-audit agent"
    },
    {
      "date": "2026-04-22",
      "iter": 157,
      "kind": "seo",
      "title": "sitemap-pages.xml now covers 24 previously-missing public routes (/activity /changelog /announcements /league /contact /coach-corner /beginner-pathway /lessons /glossary /rules-summary /scoring /spectator-info /referee-signals /target-areas /mental-game /strength /stretches /nutrition /statistics /achievements /medals /scholarship /security /code-of-conduct) so crawlers finally discover them",
      "raw": "2026-04-22 · iter 157 · seo · sitemap-pages.xml now covers 24 previously-missing public routes (/activity /changelog /announcements /league /contact /coach-corner /beginner-pathway /lessons /glossary /rules-summary /scoring /spectator-info /referee-signals /target-areas /mental-game /strength /stretches /nutrition /statistics /achievements /medals /scholarship /security /code-of-conduct) so crawlers finally discover them"
    },
    {
      "date": "2026-04-22",
      "iter": 156,
      "kind": "api+footer",
      "title": "/api/public/results endpoint (last 200 tournament results with athlete names, medal, placing, level, source) + footer now links /code-of-conduct + /security alongside /privacy + /terms in both EN and AR",
      "raw": "2026-04-22 · iter 156 · api+footer · /api/public/results endpoint (last 200 tournament results with athlete names, medal, placing, level, source) + footer now links /code-of-conduct + /security alongside /privacy + /terms in both EN and AR"
    },
    {
      "date": "2026-04-22",
      "iter": 155,
      "kind": "fix",
      "title": "/api/public/stats breakdown zeros (discipline is 'Foil'/'Épée'/'Sabre' capitalized+accented; gender is 'Male'/'Female' capitalized; normalise to lowercase ASCII before matching) + sum athlete.medals.{gold,silver,bronze} to top-level medals block",
      "raw": "2026-04-22 · iter 155 · fix · /api/public/stats breakdown zeros (discipline is 'Foil'/'Épée'/'Sabre' capitalized+accented; gender is 'Male'/'Female' capitalized; normalise to lowercase ASCII before matching) + sum athlete.medals.{gold,silver,bronze} to top-level medals block"
    },
    {
      "date": "2026-04-22",
      "iter": 154,
      "kind": "api",
      "title": "/api/public/clubs (8 clubs) + /api/public/stats (counts + weapon/gender breakdown) CORS * 600s cache, registered in /api/public index",
      "raw": "2026-04-22 · iter 154 · api · /api/public/clubs (8 clubs) + /api/public/stats (counts + weapon/gender breakdown) CORS * 600s cache, registered in /api/public index"
    },
    {
      "date": "2026-04-22",
      "iter": 153,
      "kind": "governance",
      "title": "/code-of-conduct bilingual 8-section page (who, values, expectations, prohibited, safeguarding, anti-doping, enforcement, agreement) linked to /safeguarding /anti-doping /decisions",
      "raw": "2026-04-22 · iter 153 · governance · /code-of-conduct bilingual 8-section page (who, values, expectations, prohibited, safeguarding, anti-doping, enforcement, agreement) linked to /safeguarding /anti-doping /decisions"
    },
    {
      "date": "2026-04-22",
      "iter": 152,
      "kind": "ops",
      "title": "/humans.txt + /api/health (uptime probe with intentionally minimal surface: no PID/mem/SHA)",
      "raw": "2026-04-22 · iter 152 · ops · /humans.txt + /api/health (uptime probe with intentionally minimal surface: no PID/mem/SHA)"
    },
    {
      "date": "2026-04-22",
      "iter": 151,
      "kind": "security",
      "title": "RFC 9116 /.well-known/security.txt + bilingual /security responsible-disclosure page (scope, how-to-report, in/out-of-scope, safe harbour, acknowledgments) thematic followup to pass-2 codex fixes",
      "raw": "2026-04-22 · iter 151 · security · RFC 9116 /.well-known/security.txt + bilingual /security responsible-disclosure page (scope, how-to-report, in/out-of-scope, safe harbour, acknowledgments) thematic followup to pass-2 codex fixes"
    },
    {
      "date": "2026-04-22",
      "iter": null,
      "kind": "codex",
      "title": "codex challenge mode found 4 real concurrency bugs (P1 role-requests lost-update race; P2 pending-changes same; P2 rate-limit bypass via cancelOwn splice; P2 athletes.json last-write-wins) + 2 bonus bugs (double-approval guard, ClaimWizard duplicate note fields) ALL FIXED with withFileLock wrappers, status='cancelled' marking, updateAthlete atomic helper, disabled hidden panels · 20 unit + 7+42+11 E2E all green on prod",
      "raw": "2026-04-22 10:11 · audit · codex · codex challenge mode found 4 real concurrency bugs (P1 role-requests lost-update race; P2 pending-changes same; P2 rate-limit bypass via cancelOwn splice; P2 athletes.json last-write-wins) + 2 bonus bugs (double-approval guard, ClaimWizard duplicate note fields) ALL FIXED with withFileLock wrappers, status='cancelled' marking, updateAthlete atomic helper, disabled hidden panels · 20 unit + 7+42+11 E2E all green on prod"
    },
    {
      "date": "2026-04-22",
      "iter": null,
      "kind": "crud",
      "title": "11-case write-path CRUD suite added (claim+approve, bio+approve, medical direct, emergency direct, camp registration, newsletter, permission gates x3, rate limit, UI /my) — snapshot+restore rig keeps prod data pristine · surfaced real API bug fixed: notify() was awaited and blocked redirects; switched to fire-and-forget across /api/my/claim + /api/board/approvals · 60 total E2E assertions passing on live prod",
      "raw": "2026-04-22 09:38 · audit · crud · 11-case write-path CRUD suite added (claim+approve, bio+approve, medical direct, emergency direct, camp registration, newsletter, permission gates x3, rate limit, UI /my) — snapshot+restore rig keeps prod data pristine · surfaced real API bug fixed: notify() was awaited and blocked redirects; switched to fire-and-forget across /api/my/claim + /api/board/approvals · 60 total E2E assertions passing on live prod"
    },
    {
      "date": "2026-04-22",
      "iter": null,
      "kind": "deep",
      "title": "11-case depth-audit spec added (mobile overflow, Arabic CSS hide, identity menu, ⌘K switcher, sidebar active, app grid integrity) — zero real bugs found; initial false-positive on Arabic h1 traced to Playwright :has-text matching hidden descendants · 49 total E2E assertions green",
      "raw": "2026-04-22 09:08 · audit · deep · 11-case depth-audit spec added (mobile overflow, Arabic CSS hide, identity menu, ⌘K switcher, sidebar active, app grid integrity) — zero real bugs found; initial false-positive on Arabic h1 traced to Playwright :has-text matching hidden descendants · 49 total E2E assertions green"
    },
    {
      "date": "2026-04-22",
      "iter": null,
      "kind": "modules",
      "title": "authenticated module sweep 31/31 green — every /board module + /my + /admin pages verified (HTTP 200 + expected text + no console errors + no failed requests) using production admin session · test harness committed at tests/e2e/module-sweep.spec.ts + scripts/module-sweep.sh",
      "raw": "2026-04-22 08:39 · audit · modules · authenticated module sweep 31/31 green — every /board module + /my + /admin pages verified (HTTP 200 + expected text + no console errors + no failed requests) using production admin session · test harness committed at tests/e2e/module-sweep.spec.ts + scripts/module-sweep.sh"
    },
    {
      "date": "2026-04-22",
      "iter": null,
      "kind": "icons",
      "title": "6 broken Tabler refs replaced (polls:chart-bar, approvals:checks, contracts:contract, overview empty:circle-check, settings head-coach:user-star) audited 5699 glyphs · 20 unit + 7 E2E green",
      "raw": "2026-04-22 08:30 · fix · icons · 6 broken Tabler refs replaced (polls:chart-bar, approvals:checks, contracts:contract, overview empty:circle-check, settings head-coach:user-star) audited 5699 glyphs · 20 unit + 7 E2E green"
    },
    {
      "date": "2026-04-22",
      "iter": null,
      "kind": "i18n",
      "title": "admin Arabic labels were display:none because global.css never un-hid .ar-only — added display:revert rules matching Base.astro parity; every Arabic label across /board /my sidebar topbar role cards approvals app grid module header now renders · 20 unit + 7 E2E green",
      "raw": "2026-04-22 08:18 · fix · i18n · admin Arabic labels were display:none because global.css never un-hid .ar-only — added display:revert rules matching Base.astro parity; every Arabic label across /board /my sidebar topbar role cards approvals app grid module header now renders · 20 unit + 7 E2E green"
    },
    {
      "date": "2026-04-22",
      "iter": null,
      "kind": "admin-shell",
      "title": "/my migrated onto Admin layout (user-heart icon, ModuleHeader, same topbar as /board) + full mobile pass (responsive topbar, chip collapse, tile sizing, welcome hero break-words) · 20 unit + 7 E2E green",
      "raw": "2026-04-22 08:08 · unify · admin-shell · /my migrated onto Admin layout (user-heart icon, ModuleHeader, same topbar as /board) + full mobile pass (responsive topbar, chip collapse, tile sizing, welcome hero break-words) · 20 unit + 7 E2E green"
    },
    {
      "date": "2026-04-22",
      "iter": null,
      "kind": "board",
      "title": "Odoo-style app launcher on /board home (no sidebar, category-tinted tile grid, staggered fade-in) + ModuleHeader with back-arrow and ⌘K-searchable app switcher sheet · 20 unit + 7 E2E green",
      "raw": "2026-04-22 07:52 · rearch · board · Odoo-style app launcher on /board home (no sidebar, category-tinted tile grid, staggered fade-in) + ModuleHeader with back-arrow and ⌘K-searchable app switcher sheet · 20 unit + 7 E2E green"
    },
    {
      "date": "2026-04-22",
      "iter": null,
      "kind": "board",
      "title": "BoardMemberCard on /my (4-up KPIs + next-meeting hero + open-actions list) + tab-bar edge fades + auto-scroll active into view · 20 unit + 7 E2E green",
      "raw": "2026-04-22 07:36 · polish · board · BoardMemberCard on /my (4-up KPIs + next-meeting hero + open-actions list) + tab-bar edge fades + auto-scroll active into view · 20 unit + 7 E2E green"
    },
    {
      "date": "2026-04-22",
      "iter": null,
      "kind": "ui-ux",
      "title": "App Store polish across /board + /my: IdentityChip, My.astro shell, sidebar stripe, role-card unification, ApprovalsList field diff, design tokens + motion primitives · 20 unit + 7 E2E green",
      "raw": "2026-04-22 07:25 · polish · ui-ux · App Store polish across /board + /my: IdentityChip, My.astro shell, sidebar stripe, role-card unification, ApprovalsList field diff, design tokens + motion primitives · 20 unit + 7 E2E green"
    },
    {
      "date": "2026-04-22",
      "iter": null,
      "kind": "multi-role",
      "title": "MyFamilyCard for staff-who-are-parents + Playwright E2E 7/7 green",
      "raw": "2026-04-22 07:03 · phase-final · multi-role · MyFamilyCard for staff-who-are-parents + Playwright E2E 7/7 green"
    },
    {
      "date": "2026-04-22",
      "iter": null,
      "kind": "multi-role",
      "title": "RefereeCard live (availability direct, cert renewal banner)",
      "raw": "2026-04-22 07:03 · phase 5 · multi-role · RefereeCard live (availability direct, cert renewal banner)"
    },
    {
      "date": "2026-04-22",
      "iter": null,
      "kind": "multi-role",
      "title": "SecretaryCard live (club-edit + announcement submit, write-through on approve)",
      "raw": "2026-04-22 07:03 · phase 4 · multi-role · SecretaryCard live (club-edit + announcement submit, write-through on approve)"
    },
    {
      "date": "2026-04-22",
      "iter": null,
      "kind": "multi-role",
      "title": "ParentCard live (per-child AthleteCards, add-child CTA)",
      "raw": "2026-04-22 07:03 · phase 3 · multi-role · ParentCard live (per-child AthleteCards, add-child CTA)"
    },
    {
      "date": "2026-04-22",
      "iter": null,
      "kind": "multi-role",
      "title": "AthleteCard live (medical direct, emergency direct, profile via approvals)",
      "raw": "2026-04-22 07:03 · phase 2 · multi-role · AthleteCard live (medical direct, emergency direct, profile via approvals)"
    },
    {
      "date": "2026-04-22",
      "iter": null,
      "kind": "multi-role",
      "title": "claim+approve flow deployed (OTP dispatch, /my shell, ClaimWizard, Approvals tab, notifications, role-requests+pending-changes stores)",
      "raw": "2026-04-22 06:50 · phase 1 · multi-role · claim+approve flow deployed (OTP dispatch, /my shell, ClaimWizard, Approvals tab, notifications, role-requests+pending-changes stores)"
    },
    {
      "date": "2026-04-21",
      "iter": 150,
      "kind": "feature",
      "title": "FINALE · Changelog — /changelog (public log of all 150 sprint iters, grouped by day, kind filters, sprint-complete hero) · SPRINT 150/150 COMPLETE",
      "raw": "2026-04-21 22:50 · iter 150 · feature · FINALE · Changelog — /changelog (public log of all 150 sprint iters, grouped by day, kind filters, sprint-complete hero) · SPRINT 150/150 COMPLETE"
    },
    {
      "date": "2026-04-21",
      "iter": 149,
      "kind": "feature",
      "title": "Activity timeline — /activity (unified news/events/results/media timeline, 4 time buckets, kind filter chips)",
      "raw": "2026-04-21 22:46 · iter 149 · feature · Activity timeline — /activity (unified news/events/results/media timeline, 4 time buckets, kind filter chips)"
    },
    {
      "date": "2026-04-21",
      "iter": 148,
      "kind": "feature",
      "title": "Homepage video embed — VideoFeature block after Gallery (click-to-load youtube-nocookie, lazy poster, subscribe CTA)",
      "raw": "2026-04-21 22:41 · iter 148 · feature · Homepage video embed — VideoFeature block after Gallery (click-to-load youtube-nocookie, lazy poster, subscribe CTA)"
    },
    {
      "date": "2026-04-21",
      "iter": 147,
      "kind": "feature",
      "title": "Camp registration backend — /camps/register form + /api/camps/register POST (JSON store + coaching@ email, 5 camps seeded)",
      "raw": "2026-04-21 22:37 · iter 147 · feature · Camp registration backend — /camps/register form + /api/camps/register POST (JSON store + coaching@ email, 5 camps seeded)"
    },
    {
      "date": "2026-04-21",
      "iter": 146,
      "kind": "feature",
      "title": "Share-card generator — /tools/share-card (1200x630 social card, live SVG preview, 5 accents, PNG+SVG download)",
      "raw": "2026-04-21 22:33 · iter 146 · feature · Share-card generator — /tools/share-card (1200x630 social card, live SVG preview, 5 accents, PNG+SVG download)"
    },
    {
      "date": "2026-04-21",
      "iter": 145,
      "kind": "feature",
      "title": "Coach certification — /coaches/certification (5-level pathway Assistant→FIE Master + directory of 8 certified coaches)",
      "raw": "2026-04-21 22:30 · iter 145 · feature · Coach certification — /coaches/certification (5-level pathway Assistant→FIE Master + directory of 8 certified coaches)"
    },
    {
      "date": "2026-04-21",
      "iter": 144,
      "kind": "feature",
      "title": "Inter-club league — /league (weighted points: medal value × event level multiplier, live from results)",
      "raw": "2026-04-21 22:07 · iter 144 · feature · Inter-club league — /league (weighted points: medal value × event level multiplier, live from results)"
    },
    {
      "date": "2026-04-21",
      "iter": 143,
      "kind": "feature",
      "title": "Personal training plan — /tools/training-plan (4-week generator by weapon/level/days/goal; skill/strength/sparring/recovery)",
      "raw": "2026-04-21 22:04 · iter 143 · feature · Personal training plan — /tools/training-plan (4-week generator by weapon/level/days/goal; skill/strength/sparring/recovery)"
    },
    {
      "date": "2026-04-21",
      "iter": 142,
      "kind": "feature",
      "title": "Alumni & legends page — /athletes/alumni (6 curated pioneers + auto-merge with inactive athletes + add-your-name CTA)",
      "raw": "2026-04-21 21:34 · iter 142 · feature · Alumni & legends page — /athletes/alumni (6 curated pioneers + auto-merge with inactive athletes + add-your-name CTA)"
    },
    {
      "date": "2026-04-21",
      "iter": 141,
      "kind": "feature",
      "title": "Weekly digest preview — /tools/weekly-digest (email-ready template, live next event + YTD medals + latest 3 news, copy HTML button)",
      "raw": "2026-04-21 21:29 · iter 141 · feature · Weekly digest preview — /tools/weekly-digest (email-ready template, live next event + YTD medals + latest 3 news, copy HTML button)"
    },
    {
      "date": "2026-04-21",
      "iter": 140,
      "kind": "feature",
      "title": "Announcements page — /announcements with 6 seeded notices (policy/club/competition/safeguarding/governance), type filter chips, CTA",
      "raw": "2026-04-21 20:58 · iter 140 · feature · Announcements page — /announcements with 6 seeded notices (policy/club/competition/safeguarding/governance), type filter chips, CTA"
    },
    {
      "date": "2026-04-21",
      "iter": 139,
      "kind": "feature",
      "title": "Penalty tracker tool — /tools/penalty-tracker (cards by fencer, auto-escalate 2nd yellow, CSV export)",
      "raw": "2026-04-21 20:50 · iter 139 · feature · Penalty tracker tool — /tools/penalty-tracker (cards by fencer, auto-escalate 2nd yellow, CSV export)"
    },
    {
      "date": "2026-04-21",
      "iter": 138,
      "kind": "feature",
      "title": "Training-load tool — /tools/training-load (sRPE, weekly load, ACWR, monotony, strain; localStorage, seed data)",
      "raw": "2026-04-21 20:44 · iter 138 · feature · Training-load tool — /tools/training-load (sRPE, weekly load, ACWR, monotony, strain; localStorage, seed data)"
    },
    {
      "date": "2026-04-21",
      "iter": 137,
      "kind": "feature",
      "title": "Door-check QR tool — /tools/door-check-qr (A4 printable badge, live form, goqr.me render)",
      "raw": "2026-04-21 20:25 · iter 137 · feature · Door-check QR tool — /tools/door-check-qr (A4 printable badge, live form, goqr.me render)"
    },
    {
      "date": "2026-04-21",
      "iter": 136,
      "kind": "feature",
      "title": "Coach corner blog — /coach-corner with 6 seeded posts (technique/mental/parents/career) + pitch CTA",
      "raw": "2026-04-21 20:22 · iter 136 · feature · Coach corner blog — /coach-corner with 6 seeded posts (technique/mental/parents/career) + pitch CTA"
    },
    {
      "date": "2026-04-21",
      "iter": 135,
      "kind": "feature",
      "title": "Contact page — /contact with 6-department mailto routing (general/press/coaching/clubs/scholarship/safeguarding)",
      "raw": "2026-04-21 20:16 · iter 135 · feature · Contact page — /contact with 6-department mailto routing (general/press/coaching/clubs/scholarship/safeguarding)"
    },
    {
      "date": "2026-04-21",
      "iter": 134,
      "kind": "feature",
      "title": "News archive TOC — /news/archive grouped by year+month with jump-to-year chips",
      "raw": "2026-04-21 19:56 · iter 134 · feature · News archive TOC — /news/archive grouped by year+month with jump-to-year chips"
    },
    {
      "date": "2026-04-21",
      "iter": 133,
      "kind": "feature",
      "title": "404 polish — search form + Off-the-piste copy + sitemap link",
      "raw": "2026-04-21 19:52 · iter 133 · feature · 404 polish — search form + Off-the-piste copy + sitemap link"
    },
    {
      "date": "2026-04-21",
      "iter": 132,
      "kind": "feature",
      "title": "Human sitemap — /sitemap (74 links across 12 themed sections)",
      "raw": "2026-04-21 19:47 · iter 132 · feature · Human sitemap — /sitemap (74 links across 12 themed sections)"
    },
    {
      "date": "2026-04-21",
      "iter": 131,
      "kind": "feature",
      "title": "Homepage coach CTA block — dark-gradient section between About and AthletesFeature, buttons to /lessons + /coaches + /beginner-pathway",
      "raw": "2026-04-21 19:29 · iter 131 · feature · Homepage coach CTA block — dark-gradient section between About and AthletesFeature, buttons to /lessons + /coaches + /beginner-pathway"
    },
    {
      "date": "2026-04-21",
      "iter": 130,
      "kind": "feature",
      "title": "Rules summary infographic — /rules-summary (9 emoji cards: hit/priority/length/3 cards/safety/piste/salute + cross-links)",
      "raw": "2026-04-21 19:23 · iter 130 · feature · Rules summary infographic — /rules-summary (9 emoji cards: hit/priority/length/3 cards/safety/piste/salute + cross-links)"
    },
    {
      "date": "2026-04-21",
      "iter": 129,
      "kind": "feature",
      "title": "Beginner pathway — /beginner-pathway (6-step visit→signup→basics→base→first-comp→squad, timeline chips, cross-links)",
      "raw": "2026-04-21 19:19 · iter 129 · feature · Beginner pathway — /beginner-pathway (6-step visit→signup→basics→base→first-comp→squad, timeline chips, cross-links)"
    },
    {
      "date": "2026-04-21",
      "iter": 128,
      "kind": "feature",
      "title": "Printable calendar — /calendar-print (A4 portrait, year-grouped events with date tiles, past strike-through, tape-to-wall format)",
      "raw": "2026-04-21 19:02 · iter 128 · feature · Printable calendar — /calendar-print (A4 portrait, year-grouped events with date tiles, past strike-through, tape-to-wall format)"
    },
    {
      "date": "2026-04-21",
      "iter": 127,
      "kind": "feature",
      "title": "Scholarship programme — /scholarship (commitment/coverage/eligibility/application/confidentiality/funding + confidential email CTA)",
      "raw": "2026-04-21 18:54 · iter 127 · feature · Scholarship programme — /scholarship (commitment/coverage/eligibility/application/confidentiality/funding + confidential email CTA)"
    },
    {
      "date": "2026-04-21",
      "iter": 126,
      "kind": "feature",
      "title": "Clubs CSV export — /clubs.csv (slug/name/city/active/gold/silver/bronze/score/url) + link on /club-leaderboard",
      "raw": "2026-04-21 18:52 · iter 126 · feature · Clubs CSV export — /clubs.csv (slug/name/city/active/gold/silver/bronze/score/url) + link on /club-leaderboard"
    },
    {
      "date": "2026-04-21",
      "iter": 125,
      "kind": "feature",
      "title": "Referee signals poster — /referee-signals (10 signals across 5 categories, emoji icons, bilingual descriptions)",
      "raw": "2026-04-21 18:34 · iter 125 · feature · Referee signals poster — /referee-signals (10 signals across 5 categories, emoji icons, bilingual descriptions)"
    },
    {
      "date": "2026-04-21",
      "iter": 124,
      "kind": "feature",
      "title": "Strength program — /strength (4 blocks: lower/specific/core/weapon-arm, weekly schedule, safety caveats)",
      "raw": "2026-04-21 18:26 · iter 124 · feature · Strength program — /strength (4 blocks: lower/specific/core/weapon-arm, weekly schedule, safety caveats)"
    },
    {
      "date": "2026-04-21",
      "iter": 123,
      "kind": "feature",
      "title": "Training log tool — /tools/training-log (weekly 7-day table: session/min/RPE/notes/mood + reflections, A4 portrait)",
      "raw": "2026-04-21 18:24 · iter 123 · feature · Training log tool — /tools/training-log (weekly 7-day table: session/min/RPE/notes/mood + reflections, A4 portrait)"
    },
    {
      "date": "2026-04-21",
      "iter": 122,
      "kind": "feature",
      "title": "Target areas — /target-areas (inline SVG silhouettes for foil/épée/sabre with green-shaded valid zones + off-target notes)",
      "raw": "2026-04-21 18:07 · iter 122 · feature · Target areas — /target-areas (inline SVG silhouettes for foil/épée/sabre with green-shaded valid zones + off-target notes)"
    },
    {
      "date": "2026-04-21",
      "iter": 121,
      "kind": "feature",
      "title": "Individual lessons — /lessons (4 tiers trial/foundation/performance/group + request form posting to /api/contact)",
      "raw": "2026-04-21 17:59 · iter 121 · feature · Individual lessons — /lessons (4 tiers trial/foundation/performance/group + request form posting to /api/contact)"
    },
    {
      "date": "2026-04-21",
      "iter": 120,
      "kind": "feature",
      "title": "Spectator info — /spectator-info (7 sections: what to expect, seat, etiquette, dress, photo, kids, Qs + quick-links)",
      "raw": "2026-04-21 17:56 · iter 120 · feature · Spectator info — /spectator-info (7 sections: what to expect, seat, etiquette, dress, photo, kids, Qs + quick-links)"
    },
    {
      "date": "2026-04-21",
      "iter": 119,
      "kind": "feature",
      "title": "Scoring explainer — /scoring (6 sections: electric box, target, priority, bout length, cards, spectator shortcut)",
      "raw": "2026-04-21 17:40 · iter 119 · feature · Scoring explainer — /scoring (6 sections: electric box, target, priority, bout length, cards, spectator shortcut)"
    },
    {
      "date": "2026-04-21",
      "iter": 118,
      "kind": "feature",
      "title": "Match-day kit checklist — /tools/kit-check (28 items, 6 categories: weapons/armour/clothing/hydration/paperwork/extras, A4 portrait print)",
      "raw": "2026-04-21 17:26 · iter 118 · feature · Match-day kit checklist — /tools/kit-check (28 items, 6 categories: weapons/armour/clothing/hydration/paperwork/extras, A4 portrait print)"
    },
    {
      "date": "2026-04-21",
      "iter": 117,
      "kind": "feature",
      "title": "Fencing glossary — /glossary (20 EN+AR terms, live search filter, letter-jump chips, empty-state)",
      "raw": "2026-04-21 17:16 · iter 117 · feature · Fencing glossary — /glossary (20 EN+AR terms, live search filter, letter-jump chips, empty-state)"
    },
    {
      "date": "2026-04-21",
      "iter": 116,
      "kind": "feature",
      "title": "Mental game — /mental-game (pre-bout routine, box breathing, reset after lost touch, focus cues, sleep, when to seek help) + cross-links",
      "raw": "2026-04-21 17:12 · iter 116 · feature · Mental game — /mental-game (pre-bout routine, box breathing, reset after lost touch, focus cues, sleep, when to seek help) + cross-links"
    },
    {
      "date": "2026-04-21",
      "iter": 115,
      "kind": "feature",
      "title": "Injury-prevention stretches — /stretches (6 warm-up + 7 cool-down with reps/holds/focus + 5 red-flag signals)",
      "raw": "2026-04-21 16:58 · iter 115 · feature · Injury-prevention stretches — /stretches (6 warm-up + 7 cool-down with reps/holds/focus + 5 red-flag signals)"
    },
    {
      "date": "2026-04-21",
      "iter": 114,
      "kind": "feature",
      "title": "Athlete nutrition — /nutrition (6 EN+AR sections: hydration/pre/match-day/recovery/Ramadan/supplements + medical disclaimer)",
      "raw": "2026-04-21 16:46 · iter 114 · feature · Athlete nutrition — /nutrition (6 EN+AR sections: hydration/pre/match-day/recovery/Ramadan/supplements + medical disclaimer)"
    },
    {
      "date": "2026-04-21",
      "iter": 113,
      "kind": "feature",
      "title": "Floor captain checklist — /tools/floor-captain (22-point running order, 5 phases, A4 portrait print)",
      "raw": "2026-04-21 16:43 · iter 113 · feature · Floor captain checklist — /tools/floor-captain (22-point running order, 5 phases, A4 portrait print)"
    },
    {
      "date": "2026-04-21",
      "iter": 112,
      "kind": "feature",
      "title": "Tournament registration — /register (auto-populated event dropdown, athlete/contact/medical/consent fields, POST /api/contact, 48h confirmation)",
      "raw": "2026-04-21 16:30 · iter 112 · feature · Tournament registration — /register (auto-populated event dropdown, athlete/contact/medical/consent fields, POST /api/contact, 48h confirmation)"
    },
    {
      "date": "2026-04-21",
      "iter": 111,
      "kind": "feature",
      "title": "First-aid checklist — /tools/first-aid (23-point medical readiness: personnel/equipment/venue/paperwork, tick-boxes, sign-line, A4 portrait)",
      "raw": "2026-04-21 16:18 · iter 111 · feature · First-aid checklist — /tools/first-aid (23-point medical readiness: personnel/equipment/venue/paperwork, tick-boxes, sign-line, A4 portrait)"
    },
    {
      "date": "2026-04-21",
      "iter": 110,
      "kind": "feature",
      "title": "Achievements feed — /achievements (unified newest-first stream of milestones + medals grouped per event, cross-links to history/HoF/results)",
      "raw": "2026-04-21 16:15 · iter 110 · feature · Achievements feed — /achievements (unified newest-first stream of milestones + medals grouped per event, cross-links to history/HoF/results)"
    },
    {
      "date": "2026-04-21",
      "iter": 109,
      "kind": "feature",
      "title": "Risk assessment template — /tools/risk-assessment (8 pre-filled hazards, L×S scoring, L/M/H colour chips, inline editable, A4 landscape print)",
      "raw": "2026-04-21 16:01 · iter 109 · feature · Risk assessment template — /tools/risk-assessment (8 pre-filled hazards, L×S scoring, L/M/H colour chips, inline editable, A4 landscape print)"
    },
    {
      "date": "2026-04-21",
      "iter": 108,
      "kind": "feature",
      "title": "Session plan tool — /tools/session-plan (6 phase blocks w/ minute totals, inline editable, A4 print, pre-filled template)",
      "raw": "2026-04-21 15:50 · iter 108 · feature · Session plan tool — /tools/session-plan (6 phase blocks w/ minute totals, inline editable, A4 print, pre-filled template)"
    },
    {
      "date": "2026-04-21",
      "iter": 107,
      "kind": "feature",
      "title": "Public statistics dashboard — /statistics (8 headline cards + medal-by-type/weapon/year charts, live-computed from existing data)",
      "raw": "2026-04-21 15:43 · iter 107 · feature · Public statistics dashboard — /statistics (8 headline cards + medal-by-type/weapon/year charts, live-computed from existing data)"
    },
    {
      "date": "2026-04-21",
      "iter": 106,
      "kind": "feature",
      "title": "Sitemap catch-up — 14 new URLs in sitemap-pages.xml (8 club details + 3 tools + 7 pages added across iters 65-103)",
      "raw": "2026-04-21 15:33 · iter 106 · feature · Sitemap catch-up — 14 new URLs in sitemap-pages.xml (8 club details + 3 tools + 7 pages added across iters 65-103)"
    },
    {
      "date": "2026-04-21",
      "iter": 105,
      "kind": "feature",
      "title": "Event runsheet tool — /tools/runsheet (day-of-show timeline builder, inline editable rows, A4 landscape print, pre-filled template) + Tools hub Ready",
      "raw": "2026-04-21 15:17 · iter 105 · feature · Event runsheet tool — /tools/runsheet (day-of-show timeline builder, inline editable rows, A4 landscape print, pre-filled template) + Tools hub Ready"
    },
    {
      "date": "2026-04-21",
      "iter": 104,
      "kind": "feature",
      "title": "Search filter pills — per-type result counts, auto-dim empty types, updates on every query",
      "raw": "2026-04-21 15:14 · iter 104 · feature · Search filter pills — per-type result counts, auto-dim empty types, updates on every query"
    },
    {
      "date": "2026-04-21",
      "iter": 103,
      "kind": "feature",
      "title": "Club detail pages — /clubs/[slug] dynamic routes, SportsTeam JSON-LD, filtered athlete grid, quick-links to training/venues/join",
      "raw": "2026-04-21 15:05 · iter 103 · feature · Club detail pages — /clubs/[slug] dynamic routes, SportsTeam JSON-LD, filtered athlete grid, quick-links to training/venues/join"
    },
    {
      "date": "2026-04-21",
      "iter": 102,
      "kind": "feature",
      "title": "Events tag filter — dynamic pill row above card grid, filters by event.tag, empty-state, client-side",
      "raw": "2026-04-21 14:49 · iter 102 · feature · Events tag filter — dynamic pill row above card grid, filters by event.tag, empty-state, client-side"
    },
    {
      "date": "2026-04-21",
      "iter": 101,
      "kind": "feature",
      "title": "Sponsor application — /partners/apply (5-tier pricing, full form w/ tier+budget+goals, honeypot, POST to /api/contact) + /partners CTA updated",
      "raw": "2026-04-21 14:47 · iter 101 · feature · Sponsor application — /partners/apply (5-tier pricing, full form w/ tier+budget+goals, honeypot, POST to /api/contact) + /partners CTA updated"
    },
    {
      "date": "2026-04-21",
      "iter": 100,
      "kind": "feature",
      "title": "MILESTONE Homepage medal ticker — bilingual animated marquee of 20 recent medals (emoji + athlete + event + year), reduced-motion safe, RTL-aware",
      "raw": "2026-04-21 14:37 · iter 100 · feature · MILESTONE Homepage medal ticker — bilingual animated marquee of 20 recent medals (emoji + athlete + event + year), reduced-motion safe, RTL-aware"
    },
    {
      "date": "2026-04-21",
      "iter": 99,
      "kind": "feature",
      "title": "Homepage testimonial — rotating bilingual pull-quote (President/HeadCoach/Board), dark section between Gallery and Instagram",
      "raw": "2026-04-21 14:22 · iter 99 · feature · Homepage testimonial — rotating bilingual pull-quote (President/HeadCoach/Board), dark section between Gallery and Instagram"
    },
    {
      "date": "2026-04-21",
      "iter": 98,
      "kind": "feature",
      "title": "Results medal chart — stacked horizontal bars per year (G/S/B) with counts + totals + legend",
      "raw": "2026-04-21 14:19 · iter 98 · feature · Results medal chart — stacked horizontal bars per year (G/S/B) with counts + totals + legend"
    },
    {
      "date": "2026-04-21",
      "iter": 97,
      "kind": "feature",
      "title": "Newsletter archive — /newsletter-archive (empty-state w/ live sub count + what-to-expect + inline subscribe form)",
      "raw": "2026-04-21 13:54 · iter 97 · feature · Newsletter archive — /newsletter-archive (empty-state w/ live sub count + what-to-expect + inline subscribe form)"
    },
    {
      "date": "2026-04-21",
      "iter": 96,
      "kind": "feature",
      "title": "Drills category filter — 6-pill client-side filter (Footwork/Point/Distance/Parry/Tactics/Conditioning) above card grid",
      "raw": "2026-04-21 13:52 · iter 96 · feature · Drills category filter — 6-pill client-side filter (Footwork/Point/Distance/Parry/Tactics/Conditioning) above card grid"
    },
    {
      "date": "2026-04-21",
      "iter": 95,
      "kind": "feature",
      "title": "Events CSV export — /events.csv (id/date/title/tag/photo/url) + CSV button on /events",
      "raw": "2026-04-21 13:25 · iter 95 · feature · Events CSV export — /events.csv (id/date/title/tag/photo/url) + CSV button on /events"
    },
    {
      "date": "2026-04-21",
      "iter": 94,
      "kind": "feature",
      "title": "Athletes CSV export — /athletes.csv (id/slug/name/weapon/category/club/photo/url cols) + link on /athletes filter bar",
      "raw": "2026-04-21 13:23 · iter 94 · feature · Athletes CSV export — /athletes.csv (id/slug/name/weapon/category/club/photo/url cols) + link on /athletes filter bar"
    },
    {
      "date": "2026-04-21",
      "iter": 93,
      "kind": "feature",
      "title": "Results CSV export — /results.csv endpoint + Download CSV button on /results, extracted resultsData lib for reuse",
      "raw": "2026-04-21 12:58 · iter 93 · feature · Results CSV export — /results.csv endpoint + Download CSV button on /results, extracted resultsData lib for reuse"
    },
    {
      "date": "2026-04-21",
      "iter": 92,
      "kind": "feature",
      "title": "Parents FAQ — /parents (10 EN+AR questions: age/safety/time/cost/academics/supervision/mixed/travel/quitting/talent + trial CTAs)",
      "raw": "2026-04-21 12:55 · iter 92 · feature · Parents FAQ — /parents (10 EN+AR questions: age/safety/time/cost/academics/supervision/mixed/travel/quitting/talent + trial CTAs)"
    },
    {
      "date": "2026-04-21",
      "iter": 91,
      "kind": "feature",
      "title": "Athlete comparison — /tools/compare?a=&b= SSR, side-by-side G/S/B + intl + recent date + profile link, hub promoted Ready",
      "raw": "2026-04-21 12:30 · iter 91 · feature · Athlete comparison — /tools/compare?a=&b= SSR, side-by-side G/S/B + intl + recent date + profile link, hub promoted Ready"
    },
    {
      "date": "2026-04-21",
      "iter": 90,
      "kind": "feature",
      "title": "Club medal leaderboard — /club-leaderboard (weighted rank by level+medal, G/S/B + athletes + intl + score cols, medal-coloured position chips)",
      "raw": "2026-04-21 12:17 · iter 90 · feature · Club medal leaderboard — /club-leaderboard (weighted rank by level+medal, G/S/B + athletes + intl + score cols, medal-coloured position chips)"
    },
    {
      "date": "2026-04-21",
      "iter": 89,
      "kind": "feature",
      "title": "Bout slip generator — /tools/bout-slip (printable DE match slip, red/green score boxes, card cols, signatures, 2 per A4) + promote Ready",
      "raw": "2026-04-21 11:15 · iter 89 · feature · Bout slip generator — /tools/bout-slip (printable DE match slip, red/green score boxes, card cols, signatures, 2 per A4) + promote Ready"
    },
    {
      "date": "2026-04-21",
      "iter": 88,
      "kind": "feature",
      "title": "Results filters — year + medal + search across athlete/event/discipline, count indicator, reset button, auto-hide empty year blocks",
      "raw": "2026-04-21 11:08 · iter 88 · feature · Results filters — year + medal + search across athlete/event/discipline, count indicator, reset button, auto-hide empty year blocks"
    },
    {
      "date": "2026-04-21",
      "iter": 87,
      "kind": "feature",
      "title": "Venue directory — /venues (8 club venues, EN+AR address, parking, Google/Waze/Apple maps links)",
      "raw": "2026-04-21 11:06 · iter 87 · feature · Venue directory — /venues (8 club venues, EN+AR address, parking, Google/Waze/Apple maps links)"
    },
    {
      "date": "2026-04-21",
      "iter": 86,
      "kind": "feature",
      "title": "Homepage partners strip — OOC/MCSY/FIE/FCA tiles above footer, bilingual, links out + to /partners",
      "raw": "2026-04-21 11:01 · iter 86 · feature · Homepage partners strip — OOC/MCSY/FIE/FCA tiles above footer, bilingual, links out + to /partners"
    },
    {
      "date": "2026-04-21",
      "iter": 85,
      "kind": "feature",
      "title": "Sitemap index split — /sitemap.xml is now sitemapindex with 4 child sitemaps (pages/news/events/athletes), 48 pages + dynamic entries, hreflang alternates",
      "raw": "2026-04-21 10:45 · iter 85 · feature · Sitemap index split — /sitemap.xml is now sitemapindex with 4 child sitemaps (pages/news/events/athletes), 48 pages + dynamic entries, hreflang alternates"
    },
    {
      "date": "2026-04-21",
      "iter": 84,
      "kind": "feature",
      "title": "Homepage next-event countdown — live days/hrs/min/sec to soonest upcoming event, bilingual, calendar CTAs",
      "raw": "2026-04-21 10:40 · iter 84 · feature · Homepage next-event countdown — live days/hrs/min/sec to soonest upcoming event, bilingual, calendar CTAs"
    },
    {
      "date": "2026-04-21",
      "iter": 83,
      "kind": "feature",
      "title": "Advanced athlete filters — /athletes category + club selectors + name search, match count, reset button, empty-state",
      "raw": "2026-04-21 10:38 · iter 83 · feature · Advanced athlete filters — /athletes category + club selectors + name search, match count, reset button, empty-state"
    },
    {
      "date": "2026-04-21",
      "iter": 82,
      "kind": "feature",
      "title": "News archive filters — /news year pill filters + EN/AR search, count indicator, empty-state reset link",
      "raw": "2026-04-21 10:34 · iter 82 · feature · News archive filters — /news year pill filters + EN/AR search, count indicator, empty-state reset link"
    },
    {
      "date": "2026-04-21",
      "iter": 81,
      "kind": "feature",
      "title": "FAQ rich search — live client-side EN+AR filter, match counter, clear button, empty-state CTA, auto-expand matches",
      "raw": "2026-04-21 10:21 · iter 81 · feature · FAQ rich search — live client-side EN+AR filter, match counter, clear button, empty-state CTA, auto-expand matches"
    }
  ]
}