Built in public
Changelog
سجل التغييرات
Every single thing shipped on this site, OWASP headers, referee signals, poule sheets, scholarship forms, AI translation, Arabic RTL layout, door-check QR, 150 iterations of "what would a real federation website actually do?". The log is public because the work is.
Iterations
258
Log entries
253
Days
5
Features
117
feature · 117 api · 39 ux · 29 note · 7 maintenance · 6 seo · 6
Sat, 25 Apr 2026 · 2 entries
- noteLIVE — 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)
- noteSHIPPED — 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
Fri, 24 Apr 2026 · 20 entries
- noteSHIPPED — 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
- noteSHIPPED — 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
- noteSHIPPED — 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
- noteSHIPPED — 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
- noteSHIPPED — 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
- iter258phase0Phase 0 Task 0.1 — wire orphaned SurveysTab into /board MODULE_GROUPS Governance group (after Pollsefore Decisions); 385-line tab was dead code until this commit. Badge counts open surveys
- iter257ux/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 inputrrow-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)
- iter256maintenancesmoke.sh 90->96 endpoints — covers iter 248 (/api/public/stats/birthyears)ter 255 (/api/public/stats/weapons), iter 250/251 (?ageCategory=U20, Senior, csv). 96/96 green on prod
- iter255api/api/public/stats/weapons per-weapon aggregate — 3 rows (foil/epee/sabre) with athletes countender 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
- iter254uxFIE age-category mini-chip on /athletes grid cards (top-rightharcoal with 90% opacity, same title a11y pattern as iter 252/253); renders only when birthYear is known. Verified 3 U20 card chips live
- iter253uxFIE age-category chip on athlete detail page — inline next to the Born/Age lineharcoal-pill theme matching iter 252 filter row, title='FIE age category {X}' for a11y. Verified U20 chip renders on /athletes/israa-al-siyabi
- iter252uxFIE 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
- iter251api/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
- iter250apiMILESTONE 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)enior=0. /api/public index endpoint descriptor updated
- iter249apienrich /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 18ategory U20, birthYear 2008
- iter248api/api/public/stats/birthyears athlete age distribution — FIE categories (U11/U13/U15/U17/U20/Seniornd-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
- iter247maintenancesmoke.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
- iter246api/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-datedorrect behaviour
- iter245api/api/public/stats/clubs per-club athlete aggregates (total/active + foil/epee/sabre + male/female)orted 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
- iter244api?weapon=foil|epee|sabre|mixed filter on /api/public/leaderboard + /leaderboard.csv (case-insensitiveuns through medalLeaderboard signature change). JSON echoes weapon field, CSV filename concatenates year+weapon (e.g. ofc-leaderboard-2025-epee.csv). Verified prod
Thu, 23 Apr 2026 · 78 entries
- iter243api/leaderboard.csv medal-leaderboard download — columns match /api/public/leaderboard JSON (ranklug/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
- iter242maintenancesmoke.sh 79->85 endpoints covering iter 237 (/api/public/venues)ter 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)
- iter241api/api/public/leaderboard medal aggregate — uses lib/results.medalLeaderboard (weighted 3/2/1)oins 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
- iter240api/api/public/gallery JSON endpoint — ordered by display orderrc rewritten to absolute https://fencing.om URLs, ?limit=1-500 (default 200). Registered in feeds.json + openapi + /developers. Verified count=2 with ?limit=2
- iter239api/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=200asirah-club=200, no-such=404
- iter238maintenance/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)ter 234 (/api/public/coaches), iter 236 (/api/public/coaches/{id}), iter 237 (/api/public/venues + /venues.csv). 79/79 green on prod
- iter237api/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/Arity, address, parking, mapQ + derived mapUrl. Registered in /api/public index, feeds.json, openapi.json, /developers. HTML /venues still green
- iter236api/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=200m_khalid_shuaibi=404, nope-xyz=404. Added to openapi.json + /developers
- iter235apiregister 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
- iter234api/api/public/coaches JSON mirrors the existing coaches.csv — filters team by /coach|مدرب/itrips email/phone, exposes id/nameEn+Ar/roleEn+Ar/photo/bio/certificationLevel/joinedAt. Verified 200 count=1 (Hisham Karshod)
- iter233uxsurface iter-232 per-athlete Results CSV + JSON downloads as ghost-buttons next to Passport PDF on /athletes/{slug} (bilingual labelsa-table + fa-brackets-curly icons) — verified both links render on jana-al-sharji prod
- iter232api/athletes/{slug}/results.csv + results.json per-athlete match-history endpoints (item #24 missing-features); slug→athleteId via readAthletes + resultsForAthlete + medalTally; unknown slug 404mpty roster returns header-only CSV / count=0 JSON
- iter231uxglobal '/' keybind focuses first search input on any public page (GitHub/Notion stylekips when typing in input/textarea/contenteditable); focuses+selects
- iter230maintenancesmoke.sh extended with 12 filter permutations from iters 225-229 (weaponlub, year, tag, q, limit, medal, types=) — 61→73 endpoints, 73/73 green against prod
- iter229api/api/public/results ?athleteSlug (or athleteId) + ?medal + ?year + ?weapon + ?limit (1-500efault 200) filters; store currently empty so all permutations return 200 count=0
- iter228syncrefresh 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
- iter227api/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
- iter226api/api/public/events ?year=YYYY date-prefix match + ?tag= case-insensitive exact match; verified 8 total → 3 from 2025international, 1 composed
- iter225api/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 epeeSalalah by club
- iter224uxricher 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')
- iter223api/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
- iter222api/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
- iter221discoverability/.well-known/change-password 302 to /login for W3C password-manager discovery (Bitwardenpple Passwords, 1Password, Chrome leaked-password flow); fencing.om is OTP so redirect to re-authentication page
- iter220maintenancescripts/smoke.sh 61-endpoint smoke suite (discovery/feeds/registries/entities/aggregates/search/detail/pagesTTP code + content-type substring check, exits non-zero on any mismatch, 61/61 green against prod now)
- iter219refactorDRY 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
- iter218uxSpotlightFeature 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)
- iter217api/api/public/spotlight athlete-of-the-day (FNV-1a(yyyy-mm-dd) mod active-rosterame pick all day, rotates at UTC midnight) — today's pick: Salma Al Daghishi
- iter216discoverabilityfooter bottom bar surfaces /now (iter 206) + /developers (iter 170) alongside Privacy/Terms/CoC/Security
- iter215ux'Jump to' anchor pills on /committee (President + board-members grid anchor + CSV downloadtar/users/file-csv icons, EN+AR)
- iter214uxclient-side filter on /clubs index (name+city searchFD-normalised, shown/total counter); mirrors iter-213 committee filter
- iter213uxclient-side filter input above /committee board grid (NFD-normalisedive shown/total count pill, empty-state msg, no network roundtrip)
- iter212export/coaches.csv (head coach + certified coaches filtered by coach|مدرب role patternO email/phone, 600s cache) registered in feeds.json catalog and /developers docs
- iter211seo/sitemap-images.xml Google image sitemap (event photos + news covers + athlete photos grouped by page URL with image:title contextchema 1.1, 1h cache); listed in sitemap.xml index
- iter210ux/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
- iter209api/api/public/sitemap.json (100 static pages + 51 dynamic news/events/athletes/clubs URLs deduped+sorted50 total, each with relative path + absolute URL, 600s cache)
- iter208ux/now page gets JSON snapshot + .ics subscribe + News RSS pill row (mirrors iter-178 news index patternurfaces structured siblings)
- iter207api/api/public/now JSON sibling to /now page (eventsThisWeek + upcomingNext5 with inDays + recentNews 7d + recentResults 30d00s cache, registered in /api/public)
- iter206ux/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)
- iter205uxNewsFeature on homepage (3 latest news cards inserted after Events before Galleryirrors /news index card style with date badge + line-clamp-2 title + RTL-aware arrow, 'All news' link in header)
- iter204docs/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)
- iter203api/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
- iter202api/api/public/search/suggest?q= typeahead endpoint (max 8 title-only results0s cache, NFD scoring); search.ts moved to search/index.ts to allow sibling; registered in /api/public
- iter201api/api/public/committee/[id] per-member JSON (President's record returns full safe-fieldsnknown 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)
- iter200MILESTONE/api/public/search?q=&limit=N cross-entity search (athletes+news+events+clubs+committeeFD-normalised exact>prefix>substring scoring, news falls back to body scan, snippet+score per hit, 60s cache, registered in /api/public)
- iter199a11yaria-current='page' + green tint on active Nav item (top + mobile drawer); active when pathname === target OR startsWith target+/o /news/<slug> still highlights News
- iter198ux'Built in public · N iterations shipped' centered footer line on every public page (reads max iter from content/loop-log.txt at module loadinks to /changelog, EN+AR; +log refresh to keep number current)
- iter197feed/changelog.atom.xml Atom 1.0 sibling to iter-188 RSS (100 entriesutodiscovered on /changelog via head slot, entry in /api/public/feeds.json catalog)
- iter196api/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 entryh cache, registered in /api/public
- iter195feed/news.atom.xml Atom 1.0 sibling to RSS (per-entry xml:langroper id/published/updated, author block, 600s cache) + Base.astro adds <link rel=alternate type=application/atom+xml> alongside the RSS link
- iter194uxreading-time badge on /news index cards (precomputed in frontmatter from readNewsArticle00wpm EN/180wpm AR, EN 'X min'/AR 'X د' next to date)
- iter192ux+fixborn/age badge on athlete profile when birthYear present (3 athletes today: israa-al-siyabihmed-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
- iter191api/clubs/<slug>/athletes.json sibling to iter-190 per-club CSV (same tolerant club-name matcheturns club identity + roster array, 404 on unknown, 600s cache)
- iter190export/clubs/<slug>/athletes.csv per-club roster CSV (tolerant club-name match handles 'Quriyat'/'Quryat' transliteration variantsFD-normalised, strips 'club' word) + Roster CSV card surfaced on /clubs/[slug] alongside venue + how-to-join
- iter189seoBase.astro gains <slot name='head'/> + /changelog adds <link rel=alternate rss> for iter-188 changelog.rss.xml so feed readers auto-detect it
- iter188feed/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)
- iter187api+syncrefresh in-repo content/loop-log.txt (was 1 day staleow reflects iter 186) + new /api/public/changelog JSON feed parsing same file (?limit=N, max 500) registered in /api/public; 175 entries indexed
- iter186api/api/public/disciplines reference endpoint (3 weapons foil/épée/sabre with EN+AR namesarget area, scoring rules, pageUrl) registered in /api/public, 24h cache
- iter185uxShareButton on /clubs/[slug] completes detail-page set (news + events + athletes + clubs all have prev/next nav AND share button)
- iter184refactor+uxextract 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
- iter183uxshare button on /events/[slug] mirrors iter-182 news (navigator.share native + clipboard fallback + transient flashN+AR)
- iter182uxshare button on /news/[slug] (navigator.share native dialog on mobile/PWAavigator.clipboard fallback with 'Link copied'/'تم النسخ' transient flash, EN+AR)
- iter181uxreading-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)
- iter180ux/clubs index gets CSV + JSON API + Leaderboard pill row (completes listing-page export pattern across news 178 athletes+events 179 clubs 180)
- iter179ux/athletes + /events index pages get JSON API export pill next to existing CSV/ICS pills (mirrors iter 178 news pattern)
- iter178ux/news index gets RSS + Email + JSON API CTA strip surfacing /news.rss.xml + /#newsletter + /api/public/news (3 pills below search rowN+AR, hover green-ofc)
- iter177ux'Add to calendar' button on /events/[slug] surfaces iter-176 per-event ICS endpoint (calendar-plus iconownload attribute, EN+AR labels, leads the CTA strip above 'Full medal table' and 'All athletes')
- iter176feed/events/<id>.ics per-event iCalendar export (RFC 5545 line-folding+escape-WR-CALNAME=event title, 600s cache, CORS *) sibling to /events.ics whole-calendar feed
- iter175uxprev/next nav on /clubs/[slug] (alphabetical EN sort) — completes detail-page nav set across news (172) events (173) athletes (174) clubs (175)
- iter174uxprev/next nav on /athletes/[slug] completes the trio (news iter 172 + events iter 173 + athletes iter 174); active roster sorted alphabetically by EN namedge entries single card, RTL-aware
- iter173uxprev/next event navigation on /events/[slug] mirrors iter 172 news pattern (sorted by date newest-firstTL-aware arrows, line-clamp-2 titles, edge events single card)
- iter172uxprev/next article navigation on /news/[slug] — grid 2-col older+newer cardsTL-aware arrows, line-clamp-2 titles, hover green-ofc border; edge articles show single card
- iter171api/api/openapi.json OpenAPI 3.1 descriptor (15 paths: entities+aggregates+opslug/id path params, envelope schema, Health schema, CC BY 4.0 license, contact [email protected]) registered in /api/public index, 1h cache
- iter170docs/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
- iter169export/committee.csv (idameEn, 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 *
- iter168api/api/public/news/[slug] (slugitleEn/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)
- iter167api/api/public/events/[id] individual event JSON (iditleEn/Ar, date, tag/Ar, photo, descEn/Ar, url) — 404 on unknown, 600s cache, for partner embeds and calendar integrations
- iter166api/api/public/athletes/[slug] individual athlete JSON (idlug, 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
- iter165seorobots.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
Wed, 22 Apr 2026 · 30 entries
- iter164seo/opensearch.xml OpenSearch 1.1 descriptor (EN + AR6/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
- iter163api/api/public/latest combined one-call feed (5 latest events + 5 latest news + 10 latest results) 300s cacheo widgets don't chain 3 API calls; registered in /api/public index
- iter162api+seo/api/public/upcoming (future events onlyorted soonest first, 600s cache, registered in /api/public index) + sitemap-pages dedup /partners and /hall-of-fame were each listed twice
- iter161api/api/public/committee (7 active membersd/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
- iter160seositemap-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
- iter159fix/athletes/[slug] second missing import streakForAthlete (same file) — all 13 athlete profile pages now return 200 (slugs that don't exist return 302 to /athletesy design)
- iter158fix/athletes/[slug] 500 — missing import pctLast30Days from lib/trainingaught by module-audit agent
- iter157seositemap-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
- iter156api+footer/api/public/results endpoint (last 200 tournament results with athlete namesedal, placing, level, source) + footer now links /code-of-conduct + /security alongside /privacy + /terms in both EN and AR
- iter155fix/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
- iter154api/api/public/clubs (8 clubs) + /api/public/stats (counts + weapon/gender breakdown) CORS * 600s cacheegistered in /api/public index
- iter153governance/code-of-conduct bilingual 8-section page (whoalues, expectations, prohibited, safeguarding, anti-doping, enforcement, agreement) linked to /safeguarding /anti-doping /decisions
- iter152ops/humans.txt + /api/health (uptime probe with intentionally minimal surface: no PID/mem/SHA)
- iter151securityRFC 9116 /.well-known/security.txt + bilingual /security responsible-disclosure page (scopeow-to-report, in/out-of-scope, safe harbour, acknowledgments) thematic followup to pass-2 codex fixes
- codexcodex 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 guardlaimWizard 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
- crud11-case write-path CRUD suite added (claim+approveio+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
- deep11-case depth-audit spec added (mobile overflowrabic 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
- modulesauthenticated 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
- icons6 broken Tabler refs replaced (polls:chart-barpprovals:checks, contracts:contract, overview empty:circle-check, settings head-coach:user-star) audited 5699 glyphs · 20 unit + 7 E2E green
- i18nadmin 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
- admin-shell/my migrated onto Admin layout (user-heart iconoduleHeader, same topbar as /board) + full mobile pass (responsive topbar, chip collapse, tile sizing, welcome hero break-words) · 20 unit + 7 E2E green
- boardOdoo-style app launcher on /board home (no sidebarategory-tinted tile grid, staggered fade-in) + ModuleHeader with back-arrow and ⌘K-searchable app switcher sheet · 20 unit + 7 E2E green
- boardBoardMemberCard 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
- ui-uxApp Store polish across /board + /my: IdentityChipy.astro shell, sidebar stripe, role-card unification, ApprovalsList field diff, design tokens + motion primitives · 20 unit + 7 E2E green
- multi-roleMyFamilyCard for staff-who-are-parents + Playwright E2E 7/7 green
- multi-roleRefereeCard live (availability directert renewal banner)
- multi-roleSecretaryCard live (club-edit + announcement submitrite-through on approve)
- multi-roleParentCard live (per-child AthleteCardsdd-child CTA)
- multi-roleAthleteCard live (medical directmergency direct, profile via approvals)
- multi-roleclaim+approve flow deployed (OTP dispatchmy shell, ClaimWizard, Approvals tab, notifications, role-requests+pending-changes stores)
Tue, 21 Apr 2026 · 123 entries
- iter150featureFINALE · Changelog — /changelog (public log of all 150 sprint itersrouped by day, kind filters, sprint-complete hero) · SPRINT 150/150 COMPLETE
- iter149featureActivity timeline — /activity (unified news/events/results/media timelinetime buckets, kind filter chips)
- iter148featureHomepage video embed — VideoFeature block after Gallery (click-to-load youtube-nocookieazy poster, subscribe CTA)
- iter147featureCamp registration backend — /camps/register form + /api/camps/register POST (JSON store + coaching@ emailcamps seeded)
- iter146featureShare-card generator — /tools/share-card (1200x630 social cardive SVG preview, 5 accents, PNG+SVG download)
- iter145featureCoach certification — /coaches/certification (5-level pathway Assistant→FIE Master + directory of 8 certified coaches)
- iter144featureInter-club league — /league (weighted points: medal value × event level multiplierive from results)
- iter143featurePersonal training plan — /tools/training-plan (4-week generator by weapon/level/days/goal; skill/strength/sparring/recovery)
- iter142featureAlumni & legends page — /athletes/alumni (6 curated pioneers + auto-merge with inactive athletes + add-your-name CTA)
- iter141featureWeekly digest preview — /tools/weekly-digest (email-ready templateive next event + YTD medals + latest 3 news, copy HTML button)
- iter140featureAnnouncements page — /announcements with 6 seeded notices (policy/club/competition/safeguarding/governance)ype filter chips, CTA
- iter139featurePenalty tracker tool — /tools/penalty-tracker (cards by fenceruto-escalate 2nd yellow, CSV export)
- iter138featureTraining-load tool — /tools/training-load (sRPEeekly load, ACWR, monotony, strain; localStorage, seed data)
- iter137featureDoor-check QR tool — /tools/door-check-qr (A4 printable badgeive form, goqr.me render)
- iter136featureCoach corner blog — /coach-corner with 6 seeded posts (technique/mental/parents/career) + pitch CTA
- iter135featureContact page — /contact with 6-department mailto routing (general/press/coaching/clubs/scholarship/safeguarding)
- iter134featureNews archive TOC — /news/archive grouped by year+month with jump-to-year chips
- iter133feature404 polish — search form + Off-the-piste copy + sitemap link
- iter132featureHuman sitemap — /sitemap (74 links across 12 themed sections)
- iter131featureHomepage coach CTA block — dark-gradient section between About and AthletesFeatureuttons to /lessons + /coaches + /beginner-pathway
- iter130featureRules summary infographic — /rules-summary (9 emoji cards: hit/priority/length/3 cards/safety/piste/salute + cross-links)
- iter129featureBeginner pathway — /beginner-pathway (6-step visit→signup→basics→base→first-comp→squadimeline chips, cross-links)
- iter128featurePrintable calendar — /calendar-print (A4 portraitear-grouped events with date tiles, past strike-through, tape-to-wall format)
- iter127featureScholarship programme — /scholarship (commitment/coverage/eligibility/application/confidentiality/funding + confidential email CTA)
- iter126featureClubs CSV export — /clubs.csv (slug/name/city/active/gold/silver/bronze/score/url) + link on /club-leaderboard
- iter125featureReferee signals poster — /referee-signals (10 signals across 5 categoriesmoji icons, bilingual descriptions)
- iter124featureStrength program — /strength (4 blocks: lower/specific/core/weapon-armeekly schedule, safety caveats)
- iter123featureTraining log tool — /tools/training-log (weekly 7-day table: session/min/RPE/notes/mood + reflections4 portrait)
- iter122featureTarget areas — /target-areas (inline SVG silhouettes for foil/épée/sabre with green-shaded valid zones + off-target notes)
- iter121featureIndividual lessons — /lessons (4 tiers trial/foundation/performance/group + request form posting to /api/contact)
- iter120featureSpectator info — /spectator-info (7 sections: what to expecteat, etiquette, dress, photo, kids, Qs + quick-links)
- iter119featureScoring explainer — /scoring (6 sections: electric boxarget, priority, bout length, cards, spectator shortcut)
- iter118featureMatch-day kit checklist — /tools/kit-check (28 itemscategories: weapons/armour/clothing/hydration/paperwork/extras, A4 portrait print)
- iter117featureFencing glossary — /glossary (20 EN+AR termsive search filter, letter-jump chips, empty-state)
- iter116featureMental game — /mental-game (pre-bout routineox breathing, reset after lost touch, focus cues, sleep, when to seek help) + cross-links
- iter115featureInjury-prevention stretches — /stretches (6 warm-up + 7 cool-down with reps/holds/focus + 5 red-flag signals)
- iter114featureAthlete nutrition — /nutrition (6 EN+AR sections: hydration/pre/match-day/recovery/Ramadan/supplements + medical disclaimer)
- iter113featureFloor captain checklist — /tools/floor-captain (22-point running orderphases, A4 portrait print)
- iter112featureTournament registration — /register (auto-populated event dropdownthlete/contact/medical/consent fields, POST /api/contact, 48h confirmation)
- iter111featureFirst-aid checklist — /tools/first-aid (23-point medical readiness: personnel/equipment/venue/paperworkick-boxes, sign-line, A4 portrait)
- iter110featureAchievements feed — /achievements (unified newest-first stream of milestones + medals grouped per eventross-links to history/HoF/results)
- iter109featureRisk assessment template — /tools/risk-assessment (8 pre-filled hazards×S scoring, L/M/H colour chips, inline editable, A4 landscape print)
- iter108featureSession plan tool — /tools/session-plan (6 phase blocks w/ minute totalsnline editable, A4 print, pre-filled template)
- iter107featurePublic statistics dashboard — /statistics (8 headline cards + medal-by-type/weapon/year chartsive-computed from existing data)
- iter106featureSitemap catch-up — 14 new URLs in sitemap-pages.xml (8 club details + 3 tools + 7 pages added across iters 65-103)
- iter105featureEvent runsheet tool — /tools/runsheet (day-of-show timeline buildernline editable rows, A4 landscape print, pre-filled template) + Tools hub Ready
- iter104featureSearch filter pills — per-type result countsuto-dim empty types, updates on every query
- iter103featureClub detail pages — /clubs/[slug] dynamic routesportsTeam JSON-LD, filtered athlete grid, quick-links to training/venues/join
- iter102featureEvents tag filter — dynamic pill row above card gridilters by event.tag, empty-state, client-side
- iter101featureSponsor application — /partners/apply (5-tier pricingull form w/ tier+budget+goals, honeypot, POST to /api/contact) + /partners CTA updated
- iter100featureMILESTONE Homepage medal ticker — bilingual animated marquee of 20 recent medals (emoji + athlete + event + year)educed-motion safe, RTL-aware
- iter99featureHomepage testimonial — rotating bilingual pull-quote (President/HeadCoach/Board)ark section between Gallery and Instagram
- iter98featureResults medal chart — stacked horizontal bars per year (G/S/B) with counts + totals + legend
- iter97featureNewsletter archive — /newsletter-archive (empty-state w/ live sub count + what-to-expect + inline subscribe form)
- iter96featureDrills category filter — 6-pill client-side filter (Footwork/Point/Distance/Parry/Tactics/Conditioning) above card grid
- iter95featureEvents CSV export — /events.csv (id/date/title/tag/photo/url) + CSV button on /events
- iter94featureAthletes CSV export — /athletes.csv (id/slug/name/weapon/category/club/photo/url cols) + link on /athletes filter bar
- iter93featureResults CSV export — /results.csv endpoint + Download CSV button on /resultsxtracted resultsData lib for reuse
- iter92featureParents FAQ — /parents (10 EN+AR questions: age/safety/time/cost/academics/supervision/mixed/travel/quitting/talent + trial CTAs)
- iter91featureAthlete comparison — /tools/compare?a=&b= SSRide-by-side G/S/B + intl + recent date + profile link, hub promoted Ready
- iter90featureClub medal leaderboard — /club-leaderboard (weighted rank by level+medal/S/B + athletes + intl + score cols, medal-coloured position chips)
- iter89featureBout slip generator — /tools/bout-slip (printable DE match sliped/green score boxes, card cols, signatures, 2 per A4) + promote Ready
- iter88featureResults filters — year + medal + search across athlete/event/disciplineount indicator, reset button, auto-hide empty year blocks
- iter87featureVenue directory — /venues (8 club venuesN+AR address, parking, Google/Waze/Apple maps links)
- iter86featureHomepage partners strip — OOC/MCSY/FIE/FCA tiles above footerilingual, links out + to /partners
- iter85featureSitemap index split — /sitemap.xml is now sitemapindex with 4 child sitemaps (pages/news/events/athletes)8 pages + dynamic entries, hreflang alternates
- iter84featureHomepage next-event countdown — live days/hrs/min/sec to soonest upcoming eventilingual, calendar CTAs
- iter83featureAdvanced athlete filters — /athletes category + club selectors + name searchatch count, reset button, empty-state
- iter82featureNews archive filters — /news year pill filters + EN/AR searchount indicator, empty-state reset link
- iter81featureFAQ rich search — live client-side EN+AR filteratch counter, clear button, empty-state CTA, auto-expand matches
- iter80featureWeapon check tool — /tools/weapon-check (11-point FIE safety checklist per fencerrintable cards, CSV template) + promote Ready
- iter79featurePoule sheet generator — /tools/poule-sheet (NxN printable gridanonical FIE bout order pools 4-10, V/M/TS/TR/Ind cols, A4 landscape) + promote Ready
- iter78featureBout timer — /tools/bout-timer (FIE 3x3min + priorityed/green scoreboard, 10s chime, kbd shortcuts) + promote Ready on hub
- iter77featureTools hub — /tools (pool + bracket ready'soon' tools placeholders, tool-request CTA)
- iter76featureDE bracket generator — /tools/bracket (cross-seed orderuto power-of-2 with BYEs, 8-128 tables, CSV/copy/print)
- iter75featurePool assignment tool — /tools/pools (client-side snake/sequential/random seeding from paste rosterSV/print/copy)
- iter74featureJoin OFC funnel — /join (5-step onboarding with cross-links to clubs/fees/safeguarding/anti-doping/rankings)
- iter73featureTraining times — /training (weekly grid across 9 club sessions with days/time/group)
- iter72featureEquipment guide — /equipment (3 weapons + 6-item FIE-standard safety kit + where-to-buy)
- iter71featureFees & licences — /fees (6 tiers: athlete/club/coach/ref/event/hardshipransparent OMR pricing)
- iter70featureCookie notice — /cookies (detailed table of 4 cookies + purpose + lifetime + type)
- iter69featureNational rankings — /rankings (auto-computed by weapon from resultseighted score tables)
- iter68featureDrill library — /drills (6 drills across footwork/point/distance/parry/tactics/conditioning + submit-drill CTA)
- iter67featureCamps & programmes — /camps (6 tiers: U12/U15/U20/Sen/Ref/Coach + registration CTA)
- iter66featureReferees & officials — /referees licensed list + 4-step pathway + clinic CTA
- iter65featureCoaches directory — /coaches pulls coach-role team members + certification CTA
- iter64featureVolunteer signup — /volunteer (6 role cardsorm with honeypot + checkbox interests + phone, POST to /api/contact, toast on success)
- iter63featurePartners & sponsors — /partners (4 tiers w/ accent gradients: governing/international/regional/academicartnership CTA)
- iter62featureHall of Fame — /hall-of-fame (weighted medal score by level+medalop-24 cards w/ gold/silver/bronze, landmark firsts timeline)
- iter61featureAccessibility statement — /accessibility (WCAG 2.1 AA commitment0 features, 3 known limitations, testing/review process, feedback CTA)
- iter60featureTerms of use — /terms (10 EN+AR sections) + footer Privacy/Terms links in bottom bar
- iter59featurePrivacy policy — /privacy (10 EN+AR sections: whathy, sharing, retention, rights, cookies, minors, security)
- iter58featureSafeguarding policy — /safeguarding (7 sectionstandards of behaviour, screening, reporting, ROP 9999 callout, Safeguarding Officer CTA)
- iter57featureAnti-doping page — /anti-doping (WADA Codeesting, TUE, whereabouts, education, sanctions, confidential reporting CTA)
- iter56featurePress kit — /press (fact sheetN+AR boilerplate, 8 downloads inc RSS/ICS/JSON API, media contact)
- iter55featurePublic JSON API — /api/public + /events + /news + /athletes (CORS *-min cache) for press/ministry integration
- iter54featureEvents .ics calendar feed — /events.ics VEVENTs with RFC 5545 foldingsia/Muscat TZ, subscribe CTA on /events
- iter53featureNews RSS feed — /news.rss.xml (40 itemsnclosures for cover photos, stripped-md descriptions) + autodiscovery <link> in Base
- iter52featureElections module — /elections mandate+countdown+eligibility+8-step timeline+secretariat CTAilingual, JSON-LD breadcrumb
- iter51featureAGM minutes archive — /agm (year-grouped completed meetings filtered by AGM/General Assembly keywordsmpty-state + next-AGM call-out, cross-links bylaws/events)
- iter50featureConstitution & bylaws — public /bylaws page with 10 EN+AR articlesticky ToC, JSON-LD breadcrumb, footer quick link
- iter49featureNewsletter sign-up — footer form posting to /api/newsletter/subscribe (honeypotP rate-limit, re-sub, /unsubscribe token) + /admin/newsletter list + CSV export
- iter48featureToast notifications — window.toast(msg,kind) + auto-promote ?saved/?deleted/?error URL params into toast + scrub URL
- iter47featureKeyboard shortcuts — / searchhelp, Esc close, g+letter jump to Overview/Team/Meetings/Actions/Polls/Decisions/Updates/News/Events
- iter46featureSite-wide search — /search UI + /api/search across athletes/results/news/events/decisions/policies + / keyboard shortcut
- iter45featurePublic /decisions log — ministry-transparency view of every board decisionear filter, outcome chips, cross-links governance + committee
- BATCH 3 (10 parallel sub-agents)Time-box timer + Chair private notes + Action sub-tasks + Role delegation + Member-at-risk + Growth log + Training attendance + Injury tracker + Coach private notes + Athlete goals
- iter34featurePublic /org-chart page — hierarchical board tree from team.json with Organization JSON-LD for SEO
- iter33featureBoard term tracker — termStartsAt/EndsAt/Notes on TeamMember + Overview alert card when terms expire within 90d
- iter32featureAction escalation 14d — 14+ days overdue fires WA+email to every admin + tags action as 'escalated'
- iter31featureAthlete passport PDF — GET /athletes/<slug>.passport — credit-card-sized ID card with photoame EN+AR, weapon, born, ref, medals, QR
- iter30featureHybrid Zoom/Teams/Meet link — videoUrl/videoProvider field + Join button on meeting page + WA reminder includes link
- iter29featureQuorum checker ribbon — green/amber/red status on scheduled meetingsefaults to majority of active board members
- iter28featureGroups & sub-committees — /board?t=sub-committees + 4 seed committees + admin CRUD
- BATCH 2 (10 parallel sub-agents)Conflicts + Selections + Disciplinary + MoYC + Correspondence + Contracts + Letter generator + Insurance + Grants + Budget+Expenses · all merged + tabs wired
- BATCH (10 parallel sub-agents)Decisions register + Agenda templates + Meeting RSVP + Read-receipts + AI board-chat + Discussions + Surveys + PWA + Governance hub + Motions
- iter5featureDigital Board Packet PDF — /admin/meetings/<id>.packet + buttons on both meeting views · plans/master.md + wave-a detail shipped
- iter4featureeSignatures (Boardable parity #2) — /admin/signature + /api/board/signature + draw canvas + stamp upload + signing event log
- iter3featurePolls & async voting (board-committee Boardable parity) — /board?t=polls + /api/board/poll + auto WA to eligible voters on create
- iter2planning100 missing features catalogued in docs/100-missing-features.md · 9 clusters × 8-12 features each · loop will work through these
- iter2featureAthlete Results & Medal Tracker — /admin/results + /medals + Result type + medalLeaderboard
- iter1securityOWASP baseline headers (HSTS/CSP/XFO/XCTO/Referrer/Permissions) via sequence middleware · commit 9092e55
- iter0cron 7,27,47 * * * *22-minute cadence · iteration 0 = kickoff
Sprint complete
150 iterations, one site. The next phase is whatever clubs, athletes, coaches and the board actually ask for. If something here is wrong or missing, tell us, contact form.
Raw log also available as plain text on GitHub. Site source: fencing.om monorepo.