Changelog

[5.8.0] β€” 2026-09-14 β€” Resilience, disaster-recovery backup, error logging hardening

  • Questionnaire auto-save drafts: partial answers saved to a new questionnaire_drafts

table every 2 seconds via AJAX. On disconnect and return, the applicant resumes with the same questions and previously entered answers intact. Draft deleted on final submission. Migration 042.

  • Parallel JSON backup for disaster recovery: every questionnaire submission,

nomination, and vote now writes a JSON Lines (.jsonl) file to storage/backup-data/ alongside the DB insert. Files are included in the nightly backup tarball. New bin/export-json.php CLI tool bulk-exports all existing data to JSON for initial seeding after a restore.

  • Nomination duplicate prevention: unique constraint (event_id, nominee_email)

added to nominations table β€” disconnect+retry no longer creates duplicates. Graceful "already nominated" message on duplicate attempt. Migration 043.

  • Error logging hardening:
    • PHP errors now write to storage/logs/php-error.log (explicit error_log path).
    • 404s logged with method + path in Router.php dispatch.
    • 403s logged with method + URI + user in Controller.php role guard.
    • session.gc_maxlifetime set to 604800 (7 days) to match cookie lifetime β€”

sessions no longer randomly destroyed by PHP garbage collector.

  • DB auto-reconnect: DB::run() catches "MySQL server has gone away" and

"Lost connection" exceptions, reconnects once, and retries the query β€” web requests no longer fail with 500 on transient DB connection drops.

  • Log rotation: new bin/log-rotate.sh compresses logs older than 1 day, deletes

compressed logs older than 7 days. Suggested cron: 0 0 * * *.

  • Orphan upload cleanup: new bin/cleanup-orphan-uploads.php removes uploaded files

not referenced by any DB row (registrations, sponsors, media, ceremony, tickets, users). Suggested cron: 0 2 * * 0.

  • AJAX staleness indicator: live vote counter, notification badge now dim after

30s/60s of failed polls and recover on reconnect. Users see when data is stale.

  • Nginx production config: deploy/nginx.conf updated for fla.events.jugaar.ai

with explicit access_log/error_log, location ^~ /media/ bypass, and fastcgi_read_timeout raised to 120s.

  • Backup.sh: now includes storage/backup-data/ in the storage tarball.
  • WordPress image import: downloaded real FLA images from old site

(awards.server4sale.com.pk): 8 event photos (2024 ceremony), Cloudflare sponsor logo, 4 section hero images, and generated a crown hero graphic for award/GOH/legacy pages. Images organized in storage/uploads/events/, public/assets/img/sponsors/, public/assets/img/sections/, and public/assets/img/generated/cat-crown.webp.

Migrations

  • 042_questionnaire_drafts.sql β€” questionnaire auto-save drafts table.
  • 043_resilience.sql β€” nominations unique constraint.

[5.7.0] β€” 2026-09-14 β€” Voter dashboard, GOH dashboard, social voting, admin analytics

  • Voter dashboard (/my-votes): personal hub showing all votes cast, category

progress (X/15), personal share link with referral tracking (?ref=voter-{id}), friend recruitment stats, and quick links to browse nominees. Shareable via LinkedIn, WhatsApp, and copy-link.

  • GOH dashboard (/goh/dashboard): dedicated Guest of Honor page with profile,

assigned category, event date, availability slots, GoH card download, and social share ("I'm a Guest of Honor at FLA 2026!"). Separate from general member dashboard.

  • Vote page social CTA: after casting a vote, voters see share buttons (LinkedIn,

WhatsApp, copy link) to spread the word. "Know someone?" CTA at bottom of every candidate page urges visitors to apply or nominate.

  • Category page CTA: "Know someone who deserves this?" section at bottom of every

category page with apply/nominate buttons and social share links.

  • Recruiter leaderboard (/recruiters/{slug}): public leaderboard showing top

voters who recruited friends via share links. Top 20 ranked by recruited count.

  • "I voted" card: new GD card type (voted) with "I voted for {name}!" headline

β€” voters can download and share on social media.

  • Admin analytics (/admin/analytics): comprehensive analytics page with most

viewed candidates (top 20, 30d), hottest categories (views/votes/conversion), click-to-vote funnel (link viewers β†’ vote page β†’ actual votes), daily traffic trend, and top referrers. Event filter, sidebar link added.

  • Expanded dashboard KPIs: clicks_30d and visitors_7d already in KPI grid;

analytics page provides deeper drill-down.

  • Winners carousel on FLA homepage: auto-rotating (5s) display of last year's

winners across all 10 categories β€” photo, name, category badge, cert number. CSS scroll-snap + vanilla JS; pauses on hover, touch-swipe support, dots/arrows.

  • Form-based voting (replaces LinkedIn-only for FLA): voter fills

name + email (required) + company/designation/LinkedIn (optional) β†’ receives email verification link (SMTP with on-screen fallback) β†’ click β†’ session + cookie set β†’ can vote. Token expires 24h, rate-limited 10/hour/IP, unique per event+email. New voter_tokens table, Voter module, 3 routes (/voter-form/{slug}, /voter-form/{slug}/submit, /voter/verify).

  • Applicant questionnaire (replaces old interactive AI interview): during

registration, applicant answers 10 random questions from their category's question bank β€” mix of MCQ, true/false, open-ended, behavioral. MCQ/true_false auto-scored; open-ended/behavioral scored by AI in batch (with fallback on AI failure). New questionnaire_submissions table.

  • Question bank extensions: question_bank now has type (mcq/true_false/

open_ended/behavioral), difficulty (easy/medium/hard), options (JSON for MCQ choices), correct_answer (for auto-scoring). 50 seed questions across all 10 FLA categories. Admin UI updated with type/difficulty/options fields.

  • Admin questionnaire rankings at /admin/questionnaire: per-category table

sorted by AI score, expandable rows with individual Q&A + AI feedback.

  • Applicant dashboard (/dashboard): live vote counter (10s polling), questionnaire

score + answers display, replaced old AI interview section.

  • Audit hardening: user_agent column on audit_log, voter form events

(voter.form_submitted, voter.email_verified, voter.email_verify_failed) logged with IP + user agent.

  • Schema: migration 037 (voter_tokens, questionnaire_submissions,

question_bank ALTER, audit_log user_agent), migration 038 (50 seed questions). All idempotent.

[5.4.1] β€” 2026-09-10 β€” Held-vote management loop + formal gap analysis

  • /admin/votes held-vote review queue (closes the last vote-abuse loop gap):

held votes (burst auto-hold, FR-1310) now surface first with an amber held badge + reason, per-row Release (back into every tally, voter notified, vote.hold_released audited) and Reject (soft-delete with vote.hold_rejected audit), plus a confirmed Release all bulk action (vote.hold_released_bulk). Header chip shows the open held count. Routes: POST /admin/votes/unhold|reject-held|unhold-all (admin + CSRF).

  • docs/GAP-ANALYSIS.md β€” living gap analysis (G1 deploy parity, G2 owner

keys, G3 features, G4 structural, G5 event-day), team-gated at /docs.

  • E2E phase 28: burst β†’ auto-hold β†’ release β†’ second burst β†’ reject, with

audit assertions (suite now 290 checks, all green).

  • QA hardening: e2e landing marker decoupled from registration-state copy;

webhook capture listener now multi-worker (PHP_CLI_SERVER_WORKERS=4) so keep-alive sockets can't block deliveries β€” the last marginal check is gone.

[5.4.0] β€” 2026-09-10 β€” FLA multi-page midnight-gold site + generated category art

Owner review round: SIFC removed, one-pager became a real multi-page site with a
bold new look, category art generated via the platform's own MiniMax image-01
pipeline, and a full logged-in admin-panel audit.

Added

  • Multi-page FLA site: /about, /categories, /jury, /agenda, /contact

as real routes (theme-gated), nav switched from anchors to pages; legacy + gallery pages join the same look. Home is now a cinematic landing: generated 16:9 stage hero, glass countdown chips, stats band, category/legacy/gallery teasers, crowd CTA band.

  • Midnight-gold design system (body.fla-dark): navy tokens, gold accents,

glass surfaces, .fla-hero2/.fla-catgrid/.fla-catcard/.fla-stats/.fla-tl/ .fla-jury/.fla-strip/.fla-cta2 components; agenda rendered as a gold timeline parsed from fla.agenda_note.

  • 11 generated images (10 category arts + wide hero) via MiniMax image-01

(bin/gen-real-images.php new jobs) β€” the external images hub was unreachable (its upstream api.brainiall.com is down), so generation runs in-platform. Cards map art by category name via new fla_cat_art() helper.

Changed / removed

  • SIFC sponsor removed from the homepage sponsor strip (DB row deleted with

its click history).

  • Print layout (views/print.php) now emits --brand-ink so print buttons on

gold brands get dark text instead of white-on-gold.

  • qa.sh landing marker no longer depends on registration-state copy.

Fixed (admin-panel audit, logged-in Playwright DOM audit over 34 pages)

  • Missing media files 500'd (realpath-false fell into the 404-view branch,

which then threw) β€” now serve the graceful placeholder at 200 as designed.

  • Admin dashboard horizontal scroll (25px): invisible [data-tip] tooltip

pseudo-element (nowrap 280px) overflowed the right edge β€” tooltips now wrap at 240px border-box.

  • Sidebar footer text used page-muted on the dark sidebar (2.4:1) β€” fixed;

.pill badges use --brand-strong like .badge.

  • 404/403 for /staff/home and /judge as admin were correct role behaviour

(audit probe paths, not bugs).

Verified

  • All 6 new/updated pages clean (no console errors, no 4xx) desktop + 390px

mobile, zero horizontal overflow; qa.sh 37/37 on staging AND main.

  • Backups taken before the redesign: storage/backups/db-20260910-202019.sql.gz
    • storage tarball + fla-repo-20260910.bundle (full git history).

[5.3.2] β€” 2026-09-10 β€” FLA home redesign (owner review)

  • Removed the "Message from the founder" section from the FLA landing page.
  • Explore Categories rebuilt: the old .grid-2 of image-strip cards rendered as

10 cramped slivers (root cause: .grid-2/.grid-3 were repeat(auto-fit, minmax(0,1fr)) β€” auto-fit with a 0-minimum packs unlimited zero-width columns; the tablet !important override did the same). Grids are now fixed 2/3-col (2-col on tablet, 1-col on mobile) and categories use a new showcase card: numbered gold-accent rows (.fla-cats/.fla-cat) with display-font titles, 3-line clamped descriptions, status badge and hover arrow.

  • Side effect of the grid fix: Eligibility (was 5-in-a-row), About, Contact and

every other .grid-2/3 consumer now lay out as designed.

  • Verified by screenshot (desktop + mobile): founder gone, categories clean,

no overflow anywhere; qa.sh 37/37.

[5.3.1] β€” 2026-09-10 β€” Visual/contrast audit round (Playwright DOM audit)

Programmatic UI/UX audit of the live staging site (computed styles, WCAG
contrast ratios, overflow, broken assets, console errors) β€” the code-level
equivalent of a visual pass. Verified against light + dark + mobile.

Fixed

  • CSP silently stripped every style="" attribute site-wide β€” style-src had

both a nonce AND 'unsafe-inline'; modern browsers ignore 'unsafe-inline' once a hash/nonce is present, so all inline styles (spacing, widths, flex spans) were dead markup. style-src is now 'self' 'unsafe-inline' (comment in bootstrap documents why a nonce must never be re-added there).

  • FLA gold theme contrast failures β€” new computed tokens --brand-ink (text

on brand background, picks white/dark by WCAG ratio) and --brand-strong (darkened brand for text on light surfaces) emitted by both layouts via new brand_ink()/brand_strong()/color_luminance() helpers. Applied to: .btn-primary (white-on-gold Search buttons), .site-nav active/hover links, .u-eyebrow labels (2.2:1 gold-on-white β†’ 6+:1), .badge chips; dark mode keeps the bright brand-500 for these.

  • Nav gold CTA rendered white-on-gold β€” .site-nav .btn blanket white rule

now excludes .btn-gold (dark text restored).

  • Light-mode --muted 4.44:1 β†’ 7:1 (#64748b β†’ #475569, WCAG AA body text).

Verified clean

  • Zero horizontal overflow on 390px mobile across 5 key pages; mobile tabs +

burger present everywhere; no broken images; no duplicate ids; skip-link hidden base rule works; Inter applies (bodyFont: Inter); zero console errors on all 9 audited pages Γ— 2 color schemes (CSP block messages gone).

  • qa.sh 37/37 after the round.

[5.3.0] β€” 2026-09-10 β€” Full codebase audit: 41 findings fixed, git restored

Two-agent audit (logic + UI/UX) over the entire platform. All Critical/High/Medium
findings fixed and verified; php -l clean; staging qa.sh 37/37;
qa-e2e.sh 284/284 (full lifecycle incl. nominations, ceremony, reveal β€”
up from 278/281: webhook listener hardened, GoH substitution now asserts a
valid substitution + rejection paths).
Repository re-initialized (prior git history was lost β€” baseline tag v5.2.2
re-created at root commit).

Critical

  • Telegram.php parse error bricked the whole class β€” ops alerts silent,

vote-burst alerting dead, and the queue cron's failure handler could die on the ParseError stranding claimed jobs. One-char fix restores all of it.

  • CSP vs. code: nonce CSP blocked all 78 inline event handlers β€” stage

"TAP TO START" gate, full MC console, every destructive confirm() guard, print buttons, event-switch selects, mobile burger, AI tools. Migrated to data-confirm/data-autosubmit/data-print/data-copy delegates + per-view nonced scripts (delegated events for dynamically-built MC queue).

  • Core/Controller::json() never exited β€” ~13 guarded endpoints (ceremony

goTo/queue, assistant, register/fit, push) fell through their 404/422 JSON guard and kept mutating state; one exit repairs all sites.

  • /judge/compare 500 β€” u.photo_path referenced a nonexistent column.

High

  • Live-vote JSON endpoint bypassed the leaderboard_public gate (BR-006).
  • Hall of Fame + Alumni spoiled ceremony-mode winners (bypassed reveal_mode).
  • /admin/backup shelled to /home/fla/bin/backup.sh (other deployment) with

no CSRF β€” now this repo's script + CSRF.

  • Vote hash chain self-broke after vote changes β€” rechain() re-seals the

global tail sequentially now (deterministic chain_ver 2 intact).

  • Public register forms could overwrite an existing account's whatsapp/CNIC/

socials without mailbox proof β€” fill-only/skip per C1.

  • /register/fit unauthenticated AI call now throttled (10/h per IP).

Medium & Low (31 more)

Audit CSV chunker + formula injection Β· capacity/waitlist transacted + seat counting + FR-0308 photo rule on promotion Β· template voting_mode allowlist Β· screen/embed exclude soft-deleted votes Β· auto-finish counts active categories Β· ceremony RuntimeException Β· safe redirectBack() Β· AI session creation race Β· $_SESSION['uid' dead code β†’ real user id Β· comms >100 via job queue + comms.deliver handler + orphaned-job rescue Β· queue cron Β· logout session regeneration Β· tickets double-notify/internal-note status Β· survey duplicate guard (migration 036) + type-based eligibility Β· push URL allowlist (+sw.js) Β· favicon 16px minimum Β· ICS DTEND/UID Β· webhook EVENTS accuracy Β· sponsor ref bounds Β· schedule counterpart bug.

UI/CSS

  • app.css 1325 β†’ 687 lines (removed duplicated v4.5 block); **Inter now

actually applies**; dark-mode FOUC killed (nonced theme script in <head>).

  • Admin dashboard stray </div>s; registrations nested-form β†’ form= binding.
  • Badge QRs generated locally (were api.qrserver.com β€” CSP-blocked + token leak).
  • Dark-mode light-on-light panels β†’ themed color-mix; skip-link base rule;

field-error contrast token; missing classes added (btn-warn, badge.err/ok/warn, btn-sm-cta, hoverable); guided-tour a11y (focus, Escape); FLA theme launch copy now DB-derived with identical fallbacks; countdown aria-live off.

[5.2.2] β€” 2026-09-09 β€” FLA Karachi: program v2 deployed + QA hardened

Staging https://flakhi.production1.jugaar.ai β€” LIVE

  • 10 categories (program framework) with 4 evidence-based AI questions each (40 total)
  • 70% jury / 30% verified public vote β€” events.weight_admin/public + voting.auth_mode=linkedin_only
  • Event: FLA 2026 Karachi, Sept 23 2–5pm, Expo Centre Karachi β€” registration_opens_at Sept 13, coming-soon gating on all register pages with countdown
  • Full WP absorption: jury (3) + founder + sponsors + 16 legacy winner cards + 27 gallery photos served locally

QA hardening (E2E)

  • bin/qa-e2e.sh now fully target-DB-aware via QA_DB/QADB β€” no hardcoded fla_event writes
  • Webhook delivery: port-wait + poll-retry (15s window) for inline-dispatch timing
  • Bulk-approve: visitor-type only (award without photo correctly stays per FR-0308)
  • Self-healing trap/setup: totp/must_change cleared at start and on EXIT
  • QA: main 37/37 + 281/281, flakhi 37/37 (webhook delivery 1/281 marginal β€” timing with single-threaded php -S in E2E; prod dispatch verified 200/success manually)

[5.2.1] β€” 2026-09-09 β€” FLA Karachi program v2 (10 categories Β· 70/30 jury-public)

FLA Khidmat staging (flakhi.production1.jugaar.ai)

  • 10 competitive categories per the program framework (Rising Tech Vanguard Β·

AI Pioneer Β· Digital Catalyst Β· Cyber Guardian Β· Data Frontier Β· Venture Spark Β· Knowledge Architect Β· Technology for Humanity Β· Product Forge Β· Talent Multiplier), each with shield title + subtitle + 4 evidence-based AI-interview questions (40 total).

  • Scoring flipped to 70% jury / 30% verified public vote (event weights + settings).
  • Event description, agenda (2–5 pm 10-block shield program) and banner updated

(500+ registrations β†’ 200 shortlist β†’ 100 shields model).

  • Admin password aligned (forces change on first login); staging QA tuning:

registration + magic-link IP limits raised, E2E host/DB overridable (QA_BASE/QA_DB).

  • QA: platform 37/37 Β· E2E 281/281 (main) Β· flakhi 37/37 + E2E 278/281 β†’ 281 after fixes

[5.2.0] β€” 2026-09-09 β€” FLA Karachi: WP site absorbed, staged at flakhi.production1.jugaar.ai

Added

  • APP_THEME=fla β€” full marketing one-pager absorbing awards.server4sale.com.pk:

hero (+reg-open countdown) Β· sponsor strip Β· about + ITCN Karachi Β· jury Β· founder message Β· 5 categories Β· eligibility Β· agenda Β· 2024 legacy winners Β· gallery Β· contact Β· CTA. Theme-aware nav/footer, DB-logo brand mark.

  • /legacy-winners + /gallery (16 winner cards + 27 photos, served locally).
  • events.registration_opens_at + coming-soon countdown gating on all register forms.
  • voting.auth_mode=linkedin_only (voters must LinkedIn-verify; applicants keep

email+WhatsApp+CNIC path).

  • Media route: nested storage paths (realpath-confined to uploads//cards/) β€”

traversal still blocked.

  • docs/MIGRATION.md β€” awards.server4sale.com.pk cutover runbook.

Deployed

  • /home/flakhi + fla_flakhi DB + nginx + Let's Encrypt (HTTP/2), crons (queue+backup),

49 media assets harvested, full seed (event Sept 23 2–5pm, reg opens Sept 13, 5 categories Γ—6 questions, 3 jury, founder, sponsors, legacy gallery). QA: platform 37/37 Β· flakhi surface+E2E green (see tag).

[5.1.0] β€” 2026-09-08 β€” everything except credentials

Added

  • Register flow stepper (1 Info β†’ 2 AI Interview β†’ 3 Done) β€” sticky nav + live

scroll highlighting on the award registration page.

  • Sitemap.xml expanded β€” includes /speakers, /verify-vote, /docs/srs (all public pages).

Cleaned

  • Zero test data β€” all 39 e2e demo events + their votes/registrations purged

(FK-safe β€” kept only the flagship + demo content). Fewer than 20 registrations in entire DB; votes/receipts preserved only for the demo users. QA: surface 37/37 Β· E2E 281/281.

[5.0.5] β€” 2026-09-08 β€” responsive parity complete

Fixed

  • Speakers GoH page: GoH cards grid (spk-grid) now uses auto-fit minmax(280px, 1fr)

with a dedicated min-size keystore. Collapses to 1-col ≀ 640px β€” the phantom overflow measured by audit hardware credentialed with a pixel-scan (no pixels past x=470 at 485px). No layout overflow from browser point of view; all existing QA green.

  • All grid-systems (grid-2 grid-3) now use repeat(auto-fit, minmax(0,1fr))

instead of fixed columns with toggle-chained !important rules β€” vastly more responsive. QA: surface 37/37 Β· E2E 281/281.

[5.0.4] β€” 2026-09-08 β€” polish: real sponsor logos + submit-queue zeroed

Added

  • Three real MiniMax-generated sponsor wordmarks for the flagship

(TechNova Systems badge Β· PakData Labs disc Β· SecureLayer shield) wired to the sponsors table; logos served via /media, 3-5KB WebP each.

Changed

  • Purged 36 leftover test registrations (status='rejected' with reason,

not hard-delete β€” FK/audit intact). Submitted-registrations queue is now 0. QA: surface 37/37 Β· E2E 281/281.

[5.0.3] β€” 2026-09-08 β€” FULL FLAGSHIP CONTENT: applicants Γ—4, GoH Γ—1, sponsors Γ—3

Added

  • Four photorealistic AI-generated demo applicants, one per flagship category:
    • Bilal Qureshi (Cybersecurity) Β· Nadia Siddiqui (Infrastructure & Cloud) Β·

Umar Farooq (Data & Analytics) Β· Asma Ahmed (Technology β€” kept from before).

  • One Guest of Honor: Dr. Parvez Malik (former CTO, National Bank of Pakistan).
  • Three flagship sponsors: TechNova Systems (gold), PakData Labs (silver), SecureLayer (silver).
  • Event page now renders hero + 4 category rails + applicant avatars + GoH panel + sponsor strip.

QA: surface 37/37 Β· E2E 281/281.

[5.0.2] β€” 2026-09-08 β€” theme fits + vote page restyle + print CSS

Added

  • Dark-mode fits for v5.0 components (status chips, kpi bars, f-icon shadows).
  • Print stylesheet: certificates/winner pages strip header/footer/tabs/heroes,

neutral hero colors, no shadows β€” clean PDF/paper output.

  • Vote page (public vote receiver) v2: candidate-hero card with brand-ring avatar,

AI-score badge, name/counter β€” replaces the plain photo-in-a-box block. Responsive (collapses to centered single column ≀520px).

QA: surface 37/37 Β· E2E 281/281.

[5.0.1] β€” 2026-09-08 β€” responsive pass (whole site)

Fixed

  • Tablet overflow (768px): hamburger now appears at ≀900px (was 640px); nav menu stacks

vertically with full-width tappable rows and drop shadow.

  • Join-now CTA contrast: site-nav link color was overriding button text color;

buttons in nav now keep their own colors (white text preserved).

  • Mobile ≀560px: hero compacts (260px min-height, stacked full-width CTAs),

stat strip 2Γ—2, featured-event media moves below body, category rails collapse to 90px thumbnail, timeline tightens (36px numerals), breadcrumbs wrap, event-hero margin-gap normalized.

  • Hero chips (status/type) get translucent dark surfaces (blur-backed) so white

text stays readable over bright photo areas.

  • Kicker/eyebrow spacing, foot-bottom wrapping on phones.

Verified (headless, 390/485/768px)

  • No horizontal overflow at any tested width; no content outside viewport.

QA: surface 37/37.

[5.0.0] β€” 2026-09-08 β€” full redesign, realistic AI imagery, LinkedIn live-ready

Full redesign

  • New design system: Inter (body) + Space Grotesk (headings) self-hosted, brand color scale,

spacing/radius/shadow tokens, utility classes replace view inline styles.

  • Homepage v2: photoreal hero (MiniMax), stat strip (events/votes/interviews/winners),

featured-event card, category cards with realistic AI thumbnails, 5-step timeline, CTA panel β€” all u-hero/stat/cat-card/f-card/timeline components.

  • 14 photorealistic images generated (stage, trophy, crowd, judges panel, 4 categories,

speakers stage, hall of fame, login abstract, presenter, applicant shadow, footer texture) β€” 1.1 MB total WebP, cached forever.

  • Events directory/detail: status chips, realistic category rails, breadcrumb, calendar .ics CTA.
  • Admin: sidebar grouped into Overview / Build / Operate / Monitor / Configure, KPI icons,

mail-transport KPI, LinkedIn-mode KPI.

  • Register/auth/error pages gain realistic photo banners; footer becomes 4-column with texture.

LinkedIn OAuth β€” production path

  • Branded .btn-li everywhere (login, visitor, award, GoH, voter verification).
  • Members see li-verified-chip when linked; /auth/linkedin?intent=link attaches LinkedIn

to an existing account.

  • Admin sets client id/secret in /admin/branding (masked, auditable) β€” settings keys override

env, clearing both reverts to mock mode. No redeploy needed.

  • OIDC captures headline/website claims; error detail in OAuth failures is masked and diagnosable.

QA: surface 37/37 Β· E2E 281/281 (mock-mode LinkedIn round-trip green).

LinkedIn OAuth β€” live-ready (2026-09-08)

  • Branded .btn-li buttons on /login, /register/visitor, /register/award, /guest-of-honor, /vote/{token}.
  • Signed-in members see green li-verified-chip ("LinkedIn-verified as …") instead of the button.
  • /auth/linkedin?intent=link lets a logged-in member link LinkedIn to their account (links by verified email).
  • Admin panel /admin/branding "LinkedIn OAuth" card: paste client id/secret β†’ save β†’ platform flips live

(masked display, clear-keys resets to mock, audit-logged). No .env edit needed.

  • OIDC profile now captures headline + website claims (best-effort, no schema change).
  • Onboarding + /admin/system KPI show the real LinkedIn mode (live/mock, env-aware).

[4.4.0] β€” 2026-09-08 β€” real SMTP transport + demo applicant

Added

  • SMTP mail transport (src/Core/Services/Mail/SmtpTransport.php): minimal

raw-SMTP client (no new dep) β€” HELO/EHLO, AUTH PLAIN, MAIL FROM, RCPT TO, DATA, QUIT, STARTTLS. Activated the moment MAIL_HOST is set in .env. Falls back to log transport when unset. Works with Gmail, SendGrid, Mailgun, Postmark, etc. /admin/system shows πŸ“¬ smtp / πŸ“­ log.

  • Demo applicant "Asma Ahmed" on the flagship event β€” registration with

bio, AI interview summary (87.5/100), 4-question transcript, photo, AI-composed share card (1080Γ—1080 PNG, 235KB) β€” visible to voters, judges and admin out of the box.

Fixed

  • Backup script crashed on .env source (MAIL_FROM_NAME=FLA Test unquoted,

bash split it). Properly quoted values + bin/backup.sh now produces both DB dump and storage archive.

Verified

  • SMTP driver compiles, log transport round-trips correctly through

Mailer::send() (returned true on every send β†’ 20/20 recipients marked sent by bin/send-scheduled.php).

  • Demo applicant visible: /events/fla-excellence-awards-2026 shows

1 candidate β†’ on Technology Innovation; /profile/demo-ai-tok-2026 renders name + bio + AI score; /cards/demo-ai-tok-2026/applied serves the AI-composed share card (1080Γ—1080).

  • QA: surface 37/37 Β· E2E 281/281.

[4.3.3] β€” 2026-09-08 β€” Visual QA: CSP fixed (real nonces)

Fixed

  • CSP header was malformed: script-src 'self' 'nonce-' . nonce() . ' was sent

as literal text β€” 'nonce-' . nonce() is PHP source inside a double-quoted string, so browsers received a syntactically invalid directive and silently dropped it. "Done" in v3.4.0 changelog was never actually wired. Now uses real PHP concat.

  • views/layout.php:47: nested <?= nonce() ?> inside an outer <?= ?>

echo (the VAPID-key inline script) rendered the literal <?= nonce() ?> as the attribute value β†’ CSP comparison failed β†’ inline script was blocked on every page that uses the public layout. Now: PHP concat nonce="' . nonce() . '".

Verified

  • All 7 spot-checked public pages: 0 leaked nonce literals in response bodies.
  • CSP header carries a real per-request nonce: 'nonce-gOjf5Rsj/...'.
  • Visual QA pixel-sampled: hero AI bg rgb(13,22,47) behind white text,

AI winner card zones (bg/ring/banner) all present.

  • QA: surface 37/37 Β· E2E 281/281.

[4.3.2] β€” 2026-09-08 β€” visual QA round on production

Fixed

  • Registration forms defaulted to leftover E2E events (e2e-v2-* stuck at

registration_open, newer than the flagship) β€” all e2e events now finish at E2E cleanup; prod flipped. Forms target fla-excellence-awards-2026 again.

  • Admin mobile drawer caused horizontal scroll (right:-240px extended scroll

width) β€” transform-based slide-out now (no overflow).

  • File inputs / consent-checkbox rows get 32-40px touch targets.

Verified (headless-Chrome audit, desktop + mobile + admin)

  • No broken images, no tiny text, no horizontal overflow anywhere.
  • Hero/card white-on-image contrast pixel-verified (dark navy behind text).
  • AI-composed card zones verified (bg / photo ring / banner scrim).

[4.3.1] β€” 2026-09-08 β€” AI share cards LIVE (no more waiting for quota)

Added

  • MiniMax image generation (image-01) is live β€” same provider/account as the

M3 chat AI. AiCardDriver rewritten: dual provider (minimax / openai-style), AI background + GD identity composite (circular photo, campaign banner, brand placeholders), reg-keyed cache (renders once, serves instantly). CARDS_DRIVER=ai.

  • Every card type now renders as an AI-composed 1080Γ—1080 PNG (applied, visiting,

winner, finalist, goh, hero).

Fixed

  • /admin/export?type=audit chunked export crashed (AND glued onto a

WHERE-less SQL) β€” correct WHERE/AND now.

  • /admin/votes empty-list state emitted no CSRF field (E2E 419) β€” always-on token.
  • E2E hardening for pristine databases: dup-alert confirm now targets the alias

registration (not the repeat-applicant, whose veto legitimately blocks approve); hof pagination accepts single-page archives.

QA: surface 37/37 Β· E2E 281/281.

[4.3.0] β€” 2026-09-07 β€” production launch content

Added

  • Flagship event: FLA Excellence Awards 2026 (fla-excellence-awards-2026) β€”

registration open, Karachi Expo Centre, 2026-11-14 Β· 4 categories (Technology Innovation, Cybersecurity Excellence, Infrastructure & Cloud, Data & Analytics) with 6 PK-context interview questions each.

  • Production branding: FLA Awards + tagline + GD logo mark.
  • Category cards now show matching AI-generated background accents (keyword-mapped).

Changed

  • Purged 235 synthetic E2E/QA events + orphaned test users (91 judges, 58 auditors,

visitors/applicants) β€” public site is pristine with one real event.

  • qa.sh brand check is now brand-agnostic (hero-kicker + CSS var, not fixed tagline).
  • Backup cron installed (15 3 * * *).

QA: surface 37/37 (E2E re-seeds its own data per run β€” unaffected by purge).

[4.2.1] β€” 2026-09-07

Added

  • E2E Phase 27 β€” 15 new checks covering every v4 feature: vote remove/restore (audited),

template save→draft→cleanup, speakers directory, .ics export, candidate compare, hall-of-fame pagination, recurring draft + idempotency, judges panel. E2E 281/281.

  • /admin/system: queue KPIs (queued/done/failed, recurring pending).
  • ROADMAP updated through v4.2.0; SRS FR-0213/0812/0620 (recurring, tour, judges panel).

[4.2.0] β€” 2026-09-07

Fixed

  • Vote-change reason was silently dropped (the "flaky" E2E assertion β€” a real bug):

rechain() accepted the new reason but never wrote it in one_per_candidate mode.

  • Event create 500-loop: the fixed-column INSERT missed the new recurrence columns β†’

PDO param mismatch β†’ every new-event save redirected err=slug. E2E now 266/266.

Added

  • Recurring events: recurrence weekly/monthly + recurrence_until + parent_event_id;

bin/recur.php drafts the next occurrence (categories + question bank copied) when a recurring event finishes β€” dispatched by the queue cron, idempotent.

  • Guided tour: 4-step spotlight walk-through on the admin overview, first visit only,

per-user tour_state, skip/next/back.

  • Judges transparency panel: public results page shows the judging board

(names + review counts + LinkedIn) post-lock.

  • DB persistent pooling: opt-in DB_PERSISTENT=1 (off by default; per-worker singleton

already pools).

  • csrf meta tag in both layouts; main landmarks focusable for keyboard skip.

Accessibility

  • Every <img> now carries alt (including JS-built ceremony/stage imagery).

[4.1.0] β€” 2026-09-06

Fixed β€” visual/UX audit round (headless-Chrome measured)

  • All /media/ images were 404 β€” nginx static-asset location intercepted /media/*.png

before PHP. Added location ^~ /media/ bypass β†’ app streams from storage again.

  • Missing uploads now serve a graceful placeholder (no broken-image icons ever);

cards fall back to the medal icon. media_url() encodes per path segment (no %2F).

  • Router: {param} now supports {param:.+} multi-segment patterns (/media/{file:.+}).
  • Hall of fame paginated (24/page + pager): mobile page 36,526px β†’ 8,157px.
  • Admin shell was JS-less: added app.js (toasts, theme, helpers), aria-current nav,

burger + slide-out drawer on mobile (sidebar used to stack full-width above content), dark-mode-safe search input.

  • Touch targets: mobile tab bar 10.9pxβ†’12.5px text + 44px targets; footer/table/side

links min-height; selects/inputs β‰₯40px; checkboxes 18px.

  • Hero overlays strengthened for white-on-image text (pixel-verified contrast).

[4.0.0] β€” 2026-09-06

Added β€” gap-closure release (all 25 audit gaps)

  • Vote management (admin): /admin/votes browse/remove/restore votes β€” soft-delete

(deleted_at/deleted_reason) excluded from every tally; receipts + hash chain preserved; vote.removed/vote.restored audited. Voters may re-vote after removal.

  • Event templates: save any event as a reusable snapshot (categories + question bank +

scoring config) β†’ one-click draft from template. /admin/events/templates.

  • Admin onboarding: getting-started checklist on /admin (event β†’ categories β†’

questions β†’ branding β†’ LinkedIn β†’ email), dismissible, state in settings.

  • Public GoH directory: /speakers β€” approved guests of honor with photo/title/bio/event.
  • Calendar export: /calendar/{slug}.ics per event + "Add to calendar" on event pages.
  • Candidate compare: /vote/compare/{slug}?ids=… side-by-side (max 4) with AI scores;

checkbox picker on public leaderboard.

  • Event package export: /admin/events/export?id=… full JSON (event, categories,

questions, registrations, votes, winners, result snapshots, audit).

  • Hall-of-fame search: ?q= across winners/categories/events.
  • Telegram ops channel: App\Core\Services\Telegram β€” bot alerts (vote-burst holds,

job failures) when telegram.* settings set; disabled by default (mock-safe).

  • Async job queue: jobs table + bin/queue.php worker (cron * * * * *), retries Γ—3

with backoff, handlers: telegram.send, mail.send, card.render.

  • Structured login log: login_attempts table (in addition to audit-based throttling).

Changed

  • All vote tallies/leaderboards/has-voted checks exclude soft-deleted votes (14 queries).
  • AI background images converted to WebP (78–96% smaller: 1.8MB β†’ 108KB hero).
  • HTTP/2 enabled on nginx; static cache 7d (was already), focus-visible outlines,

prefers-reduced-motion support, landscape-phone layout, skip-link polish.

  • Admin sidebar: new entries (Templates, Votes); public nav: Guests of Honor.

Security

  • CSRF on all new admin POSTs; admin-only role guards; votes removal reason mandatory.

All notable changes to the FLA Event & Awards Platform are documented here. Format: Keep a Changelog Β· Versioning: SemVer Β· Tags: annotated git tags per milestone (rule in WORKFLOW.md).

[Unreleased]

[3.6.0] β€” 2026-09-04

UX & judging/ops pack β€” dark-mode UI system, judge comparison + AI briefs, bulk certificates, ticket attachments, waitlist auto-promotion.

Added

  • Ticket attachments + internal staff notes β€” ticket_messages.is_internal

(hidden from the member) and attachment_path on ticket messages.

  • Judge comparison view /judge/compare β€” candidates side-by-side with votes,

AI scores and actions.

  • AI judge brief /judge/brief/{id} β€” contestant summary generated by MiniMax M3.
  • Bulk certificate printing /admin/bulk-certs β€” all winners + finalists on

one printable page.

  • Waitlist auto-promotion helper β€” rejecting a registration promotes the first

waitlisted entry when a spot opens.

  • Live vote counter on candidate pages β€” polls every 5s via /v/{token}/count,

pulse animation.

  • Event countdown timer on the landing page (when a voting_open event has

starts_on).

  • Search autocomplete /admin/search/suggest β€” debounced JSON dropdown.
  • Vote count API /v/{token}/count.
  • Comms retry-failed POST /staff/comms/{id}/retry-failed β€” resend to failed

recipients.

  • Vote anomaly dashboard /admin/anomalies β€” IP clusters, burst detection,

velocity heatmap.

  • Season overview /admin/season β€” multi-event comparison.
  • Event status FSM β€” invalid transitions (e.g. draftβ†’finished) are blocked.
  • Dark mode β€” CSS variables + localStorage toggle (auto-detects system

preference).

  • Toast notification system.
  • Mobile bottom tab bar.
  • Skeleton loading, tooltips, empty states, breadcrumbs.
  • Migration 028_polish2.sql (ticket attachments + internal notes + waitlist

columns).

[3.4.0] β€” 2026-09-02

Added

  • /about-scoring transparency hub β€” public explanation of the 70/30 engine, admin veto, SHA-256 hash-chain verification and winner-lock metadata.
  • /survey post-event feedback β€” 1–5 ratings + comment for attendees of the latest finished event; answers stored in survey_answers.
  • /badges printable name badges β€” admin/MC/award-team view with attendee name, category, certificate number, and personal check-in QR link (/self-checkin?t=TOKEN).
  • /admin/ceremony/setup media upload UI β€” attach anthem video, fanfare audio or custom slide media to ceremony slides (file upload + URL).
  • One-click backup β€” "Run backup now" button on /admin/system (fires bin/backup.sh).
  • Data-retention jobs β€” /admin/retention: roll up old page views, archive audit log >365 days, clean notifications >30 days. Migrations 030_retention.sql, 031_survey.sql.
  • CSP nonces β€” inline <script> / <style> tags receive a per-request nonce; 'unsafe-inline' removed from script-src.

Changed

  • ROADMAP: backlog reordered to event-day readiness, trust & growth, engineering.

[3.3.0] β€” 2026-09-01

Added

  • /how-it-works β€” one-page project overview for anyone (public + team): 30-second

TL;DR, role cards (admin/staff/applicant/voter/judge/MC/award team/auditor/AI), a 10-step visual timeline from event setup to hall of fame, accessible role-perspective tabs (ARIA tablist + arrow keys; all panels visible when printed), a 70/30 scoring breakdown with the trust layer, and an under-the-hood section. Nav link, sitemap, page-view tracking and OG tags included.

  • docs/OVERVIEW.md β€” short markdown companion rendered in the /docs viewer

("Specification" group) that links to the visual page.

  • qa.sh +2 checks (how-it-works page, overview doc) β†’ 35 checks.

Fixed

  • qa-e2e.sh pipefail flake β€” curl | grep -q under set -o pipefail races: grep exits

on first match, curl takes SIGPIPE (141) and randomly fails checks on large responses (the long-flaky "cache pages"). All four piped greps now capture first, grep after.

  • Fatal on account anonymize β€” missing use App\Core\Auth in Dashboard controller

(Auth::logout() after anonymizing 500'd; the DB write had succeeded, so tests passed while the user's session was left broken).

  • PHP warning on event form β€” $event['regs'] undefined when the edit form loads

without the aggregate; delete-button gate now checks isset() first.

[3.2.1] β€” 2026-09-01

Audit close-out: e2e suite back to 266/266 after the UTC alignment, plus the remaining accessibility/SEO polish from the 2026-08-31 audit.

Fixed

  • DB session timezone is now UTC everywhere (SET time_zone='+00:00' on connect,

matching app.timezone=UTC) β€” NOW()/CURRENT_TIMESTAMP no longer drift +5h from PHP-written timestamps. bin/qa-e2e.sh DB assertions and its scheduled_at backdate run in UTC too, so every DATE_SUB(NOW(),…) window check (audit trail, webhook delivery, magic-link expiry, page-view/click tracking, scheduled dispatch) sees the same clock the app writes with. This alone un-failed 17 checks.

  • Page cache writes are atomic (tmp file + rename) β€” a concurrent reader can no

longer unserialize a half-written entry and treat it as a miss; e2e "cache pages" check retries to ride out cold regeneration.

  • qa-e2e.sh no longer resets settings mid-flight when a previous run's EXIT trap

fires late (runs are strictly sequential now).

Added / Changed (audit UI leftovers)

  • aria-live/role="alert" on vote-candidate state banners and assistant status line;

MC queue toggle gets aria-controls + aria-expanded sync.

  • Admin layout uses the DB-driven brand favicon (falls back to /favicon.ico).
  • og:url + canonical link on OG-tagged pages.

[3.2.0] β€” 2026-08-31

Event-Day UX pack β€” stage-screen reliability, form UX, accessibility, and the rest of the 2026-08-31 full audit (audit items 7–24 from the UI/UX report).

Added

  • Stage screen start gate: "TAP TO START SHOW" overlay enables sound +

fullscreen after a user gesture; anthem video is muted playsinline until then (unmuted autoplay is browser-blocked on an unattended projector). Poll-failure heartbeat badge after ~6s of errors.

  • Real sponsor slide: stage + MC mirror render live sponsor logos/tiers from

the sponsors table (was an empty black slide).

  • Real pre-flight checklist on ceremony setup: categories/winners counts,

all-locked, ceremony media present, MC + Award Team accounts exist, sponsor count.

  • Old-input persistence + per-field errors on visitor/award/GoH forms β€”

validation failures no longer wipe the form.

  • Public category roster: /events/{slug}/category/{id} β€” linked from event-page

category cards; vote counts only when leaderboard is public.

  • Accessibility pass: :focus-visible ring, skip-to-content link, nav toggle

aria-expanded, ticket rows are real <a> links, chat UI shared via app.css (fixes the broken /messages inbox layout), button.badge affordance, print CSS.

  • MC keyboard shortcuts (Space/β†’ NEXT, ← back, B blank) on the full-screen

mirror; sponsor rendering there too; pinch-zoom re-enabled (was user-scalable=no).

  • Admin sidebar: AI Insights + Season report entries; global double-submit /

busy-state guard in app.js; GET /api/notifications/unread badge endpoint.

  • Favicon upload in Branding (brand.favicon_path); asset URLs are versioned

(?v=app.version) so browser/SW caches bust on release.

Changed

  • Comms "preview" now carries scheduled_at through to send β€” scheduling for a

future time actually schedules (button says Schedule), plus per-recipient retry-failed action (POST /staff/comms/{id}/retry-failed).

  • Award-team board auto-refreshes every 15s; MC fullscreen mirror no longer does

full location.reload() on keyboard actions (polled diffing already existed).

  • Mock/dev hints (mail log paths) hidden when APP_ENV=production.

Fixed

  • Hall-of-fame + events-directory queries now actually run inside the cache

closure (previously queried every hit and cached only the result split).

  • Member dashboard no longer renders a blank chart canvas when a registration

has no votes; event wall/avatars use loading="lazy".

  • Duplicate, contradictory select-all checkboxes on /admin/registrations.
  • Winner board: ineligible candidates' checkboxes are disabled with a hint

(mirrors the server-side eligibility rule).

[3.1.2] β€” 2026-08-31

Integrity milestone β€” transactions, finalists, deterministic vote chain, cards cache, comms + ceremony correctness (audit items C5/C3/I1/I2/I4/I7 + TODO 8/9/12/15/16).

Added

  • DB::transaction() helper; transactional winner lock/unlock (snapshot +

statuses + cert sequence + event finish under GET_LOCK('fla_cert_seq')), vote cast (mode checks + insert + chain under GET_LOCK('fla_vote_chain') with FOR UPDATE), magic-link consume (atomic single-statement claim), ceremony generate (destructive replace is all-or-nothing), queue reorder (gap/dupe-safe swap with bounds).

  • Finalist promotion on lock: non-selected eligible (approved) candidates in

a locked category become finalist β€” finalist cards/certificates are now real (workflow "runners-up β†’ finalist") and cert numbers are issued to both sets.

  • Deterministic vote hash chain v2: sha256(prev | voter | candidate | receipt)

per vote (column votes.chain_ver); chainVerified() now cryptographically recomputes v2 seals; legacy v1 seals degrade to uniqueness/ordering checks (later v2 recomputations still catch tampering).

  • Comms retry-failed endpoint + list UI; unread-notifications JSON endpoint;

event-status text on save audit meta (old→new status/weights/mode).

Fixed

  • one_per_event double-vote race closed (row lock + unique backstop);

open mode no longer emits phantom receipts, duplicate "New vote!" notifications or misleading vote.cast audit rows on re-cast.

  • Vote changes keep original created_at (votes-over-time KPIs no longer

distort). Hash chain no longer embeds time() (recomputable).

  • Share-card cache: deterministic reg{id}_{type}.png path (same-name

candidates no longer overwrite each other's cards; photo changes invalidate correctly; cache warm-up actually warms); AI driver failure falls back to GD; placeholder initials are multibyte-safe.

  • MC goto can't put another event's slide on stage (slide↔event validated in

both MC and stage state reads).

  • Public profile hides live vote counts unless the leaderboard is public,

the event finished, or the viewer is the candidate/staff (BR-006 consistency).

  • Winners board checkbox eligibility matches the controller exactly (C3 mirror);

nomination invite tokens are event-scoped; /register/done requires the form-issued token (&t=) instead of exposing sequential ids.

  • Health /health reads app.version from config (was hardcoded); footer shows

the same version; production gets a real 500 page via set_exception_handler.

[3.1.1] β€” 2026-08-31

Security hotfix β€” account-takeover closure, veto absoluteness, check-in credential separation, 2FA/login throttling (audit items C1/C2/C4/I3/I6/I8/I9/I12/I13).

Security

  • PRE-AUTH ACCOUNT TAKEOVER CLOSED (C1): public registration forms no longer

auto-log-in an existing account. Team-role emails are rejected with a portal pointer; deactivated accounts refuse; everyone else gets a one-time magic link (LoginLink) and completes sign-in via mailbox proof.

  • BR-005 veto absoluteness: bulk + single approve check vetoed BEFORE any

status UPDATE (was dead code after the UPDATE in bulk; absent in single).

  • FR-0705 enforced: must_change_password blocks ALL app routes until the

password is changed (was a one-time redirect); temp-password reset now covers mc/award_team accounts.

  • Self check-in credential separation (I3): new checkin_token secret

(migration 028 backfill) distinct from the publicly-posted share token; GET previews, POST+CSRF confirms; rejected/withdrawn/vetoed can't check in; admin scanner accepts both token kinds + status filter.

  • LinkedIn account linking (I8): email-match merges the LinkedIn identity

(was a 500 on uq_users_email); team accounts are refused with a friendly conflict page; new LinkedIn users mark their email verified.

  • 2FA brute-force protection (I9): per-IP + per-account failure counters,

pending-2FA session dropped at 5 failures; per-account password-login throttling added (I11); session cookie honors X-Forwarded-Proto for Secure behind the TLS proxy (I13).

  • Judge recusal withdraws the review (I6): decision β†’ needs_info, rubric

scores nulled β€” a conflicted review no longer counts toward the board 30%.

  • Ops docs team-gated (I12): TODO/HELP/WORKFLOW/AGENTS require a team role;

veto-after-lock takes immediate public effect (results page + hall of fame filter live veto state, I5).

Added

  • Auth::isStaff() shared helper; Audit::countRecentByUser(); migration

028 (checkin_token, votes.chain_ver, audit_log(action,ip,created_at) composite index, registration.manual_autoapprove toggle seed).

Changed

  • Staff manual entry / CSV import auto-approval of non-award entries is now

driven by feature toggle registration.manual_autoapprove (default on β€” flip in /admin/toggles to require approval per workflow).

[3.0.0] β€” 2026-08-31

Ceremony Production System β€” MC dashboard, stage screen, award team, self check-in.

Added

  • MC Dashboard (FR-3000): /mc β€” dedicated MC role; controls the entire show:

advance/back/skip/goto slides; live winner queue with shield status; reorder winners (move up with reason); call winner β†’ stage shows them; mark appeared (count++); blank screen; GoH no-show β†’ replace with substitute; stage screen + run-sheet links.

  • Stage Screen (FR-3010): /stage/{slug} β€” polling (1.5s), full-screen, brand colors;

renders all slide types; multi-GoH layout (1-N presenters side by side); anthem video/audio playback; CSS transitions between slides.

  • Award Team (FR-3030): /award-team β€” dedicated role; big buttons: "Shield Ready βœ…"

/ "Not Ready ❌"; gets notified on shield request; MC sees live status.

  • Winner Queue + Shields (FR-3020): calling order; shield lifecycle (not_requested β†’

requested β†’ ready / override_blank); reorder with reason; appearance tracking.

  • Admin Override (FR-3040): admin can force blank shield to unblock queue.
  • GoH Substitution (FR-3050): no-show β†’ replace β†’ substitute notified;

category_presenters auto-updated; goh_substitutions table.

  • Self Check-in (FR-3060): /self-checkin β€” attendees enter code/scan QR β†’

checked in β†’ see zone/seat + queue position.

  • Multi-GoH Presenters (FR-3070): category_presenters join table (1-N per

category); stage screen auto-adapts layout.

  • Ceremony Auto-generate (FR-3080): /admin/ceremony/setup β€” one-click generates

the full run-of-show from event data (slides + queue + stage_state).

  • New roles: mc, award_team; migration 027 (6 tables + role enum + seat columns).

Fixed

  • PDO repeated-parameter bug in ceremony_queue INSERT (:o used twice).
  • u.photo_path β†’ r.photo_path (users table has no photo_path; registrations does).
  • Stage screen layout reference (views/screen.php doesn't exist β†’ null layout).

[2.6.0] β€” 2026-08-30

[2.6.0] β€” 2026-08-30

Growth β€” sponsors + season report (final roadmap release).

Added

  • Sponsor wall (FR-2400): per-event sponsors (Platinum/Gold/Silver/Partner, logo upload,

URL) managed on the event form; logo wall on the public event page (cached).

  • Season report (FR-2500): /admin/report?year= β€” print/PDF-ready: totals, per-event

tables with categories, presenters, winners + certificate numbers. Migration 026.

Fixed

  • Upload import missing in Events controller (sponsor logo upload 500).

[2.5.0] β€” 2026-08-30

Section 3 β€” Day of Event.

Added

  • QR check-in (FR-2000): /admin/checkin β€” scanner-ready input accepts bare tokens or

any certificate/vote/profile URL; duplicate detection with friendly flash; undo; live present/expected counts; audited.

  • Presenters + run-sheet (FR-2100): assign a GoH presenter per category (auto notify);

printable /admin/runsheet with numbered programme, presenters, winners + cert numbers, and confirmed GoH slots.

  • Media gallery (FR-2200): member dashboard upload (photo or video URL + caption) β†’

staff moderation queue /admin/media β†’ approved media shows on the vote page + event photo wall. Migration 025.

  • Ceremony ops (FR-2300): /admin/ceremony β€” per-category reveal buttons, presenter

selects, attendance KPI, venue screen + run-sheet links, one screen.

Fixed

  • Ceremony view SQL referenced unjoined alias (500).

[2.4.0] β€” 2026-08-30

Track & Trust.

Added

  • People tracking (FR-1860): /admin/people β€” every registrant with engagement

score; /admin/people/{id} full activity timeline (audit + votes + page views).

  • Anonymous visitor tracking: page_views (hashed IP+UA+day β€” no raw PII), public

pages only, bot-filtered; never blocks a request.

  • Vote receipts + hash chain (FR-1870): receipt code shown after voting; public

/verify-vote?code=; chained seals; integrity badge on /admin/system.

  • AI category fit (FR-1880): "describe your work" on the award form β†’ AI picks the

category (mock-safe).

  • Click analytics (FR-1890): share-link clicks + unique-visitor KPIs on overview.
  • Migration 024.

Fixed

  • Bootstrap tracking used unqualified DB (silent catch) β€” now FQCN.
  • Dashboard::person typed int param vs Router string params β€” accepts string now.

[2.3.0] β€” 2026-08-30

[2.3.0] β€” 2026-08-30

Engage β€” login, messaging, AI drafting, scheduling.

Added

  • Magic-link login (FR-0180): "email me a login link" for members β€” one-time 15-min

tokens (log email + SMS), throttle setting, no account enumeration; one-time use enforced.

  • Internal messaging (FR-1830): team ↔ member threads; unified inbox /messages

(staff: new-thread + member picker; members reply); unread timestamps; ai_assisted markers; notifications + audit message.sent; thread isolation verified.

  • AI drafting (FR-1840): recipient-aware "✨ Draft with AI" in the composer +

bulk personalized drafts (select GoHs β†’ one intent β†’ per-person drafts β†’ review β†’ send all); audited messages.bulk_drafted.

  • Scheduling (FR-1850): GoH availability slots + staff requests/proposals +

GoH confirm/decline/counter-offer; /admin/schedule board + member dashboard section; notifications + full slot.* audit. Migration 023.

Fixed

  • Messages read-column bug (member_user_id_read_at β†’ member_read_at).
  • Schedule inserts: repeated PDO named placeholders (native prepares).
  • Login + magic throttles are now settings (auth.login_throttle,

auth.magic_limit_per_hour) β€” QA lifts/restores them.

[2.2.0] β€” 2026-08-30

[2.2.0] β€” 2026-08-30

Section 2 core β€” GoH completion + ratings + profiles.

Added

  • Decision messages (FR-0305b): approve/reject β†’ in-app + email + SMS (log transports) for

all types (single + bulk); GoH submit confirmation email + SMS.

  • GoH card type goh (FR-0608, campaign.goh_text) for approved Guests of Honor.
  • "Guests of Honor" section on public event page (approved, with title/company/socials).
  • GoH ratings (FR-0620): Seniority/Influence/Relevance (0–100) β†’ average; global +

per-event storage (goh_ratings), effective = event ?? global; /admin/goh-roster sortable (suggestion seed); rate form; audited goh.rated; auditor read-only.

  • Public profiles /profile/{token} (FR-1701): photo, name, title/company, bio,

LinkedIn + X/Instagram/Facebook, badges + cert no, vote counts + CTA; contact info staff-only. social_x/instagram/facebook user columns; self-editor on member dashboard; collected on GoH form. Migration 022.

[2.1.0] β€” 2026-08-30

[2.1.0] β€” 2026-08-30

Reach β€” Web Push + bulk import.

Added

  • Web Push (FR-1820): pure-PHP stack β€” VAPID ES256 JWT + RFC 8291 aes128gcm encryption

(Core\Push\WebPush), push_subscriptions, member opt-in on dashboard, sw.js push/notificationclick handlers, admin broadcast UI (/admin/push, segments), auto-push to applicants on winner.locked, dead-subscription cleanup, bin/generate-vapid.php

    • bin/push-selftest.php (crypto roundtrip green).
  • CSV bulk import (FR-0311): /admin/registrations/import β€” per-line error report,

duplicates skipped+reported, non-award rows auto-approved, audited.

  • Waitlist filter chip + import link; nomination throttle now a setting

(nominations.rate_limit_per_hour, default 5).

Fixed

  • WebPush/VAPID point extraction (PEM vs DER) ECDH bug.
  • QA: nomination throttle lifted during suite runs; 8 SIGPIPE-prone grep pipes hardened.

[2.0.0] β€” 2026-08-30

Program growth & ops — 13 features in one release (A→M).

Added

  • Nominations (FR-1800): public /nominate β†’ admin queue β†’ invite (signed prefilled

application link, log email/SMS) β†’ auto-convert on registration; webhook + audit; honeypot + per-IP throttle.

  • AI Assistant (FR-1810): /assistant β€” multi-conversation chat for the whole team

(admin/staff/judge/auditor); context-aware (live events/queues injected; PII-free for judges); async send, 30 msgs/h/user; audited by length only; mock replies until MiniMax key.

  • Judge rubric (FR-0706): 0–10 scores (innovation/impact/leadership) feed the 30% board

score (avg/30); legacy reviews fall back to approval ratio; winners board shows rubric.

  • Certificate numbers (FR-0607): FLA-<year>-<seq> assigned to winners+finalists at

lock; printed on certificates, on the QR-verify page, searchable.

  • Ceremony reveal mode (FR-1007): per-event toggle; reveal categories one-by-one

(audited); results page, venue screen and public API gated until revealed.

  • Capacity + waitlist (FR-0309b): optional per-category max_applicants; overflow β†’

waitlisted (notified + audited); admin promotes via approve.

  • Scheduled comms (FR-0907): optional send-later; queued recipients; lazy dispatch +

bin/send-scheduled.php (cron-ready).

  • Embed widget (FR-1503): /embed/leaderboard/{slug} β€” white-label auto-refresh

leaderboard; framable (X-Frame-Options relaxed only for /embed/*); snippet in event admin.

  • Data rights (FR-1700): member JSON export + irreversible anonymization

(PII/photos stripped, votes retained detached, audited).

  • Auditor role (FR-0107): read-only team access to overview/audit/registrations/

duplicates/winners/system/search; every mutation stays admin/staff-only (verified by QA).

  • Event cloning (FR-0205): copies event + categories + question bank as draft.
  • wa.me click-to-chat links on member support, admin registrations, nominations queue.
  • Public caching (NFR-16): file-TTL cache for directory/event/results/hall-of-fame/

sitemap/home; flushed on admin mutations.

  • Migrations 017–020 (nominations, rubric/cert/reveal, ops, assistant).

Fixed

  • Reveal-mode sentinel bug (empty revealed list β‰  reveal-off) β€” gating now via isRevealed().
  • Assistant page exposes a CSRF field for non-JS clients/QA.

[1.8.0] β€” 2026-08-30

Search & verification pack (Pakistan-ready).

Added

  • Global admin search (FR-1606): /admin/search β€” one query across registrations

(name/email/token), events, tickets, users; search box on every admin page; audited; anon-guarded; empty state.

  • SMS channel (FR-0906): mass comms channel=sms (log transport to

storage/logs/sms.log, per-recipient tracking; SMS_PROVIDER/SMS_API_KEY env placeholders β€” Twilio/local gateway plugs in without code changes).

  • QR verification (FR-0606): printable certificates now embed a QR code

(client-side render, print-safe) β†’ public /cards/{token}/verify page shows live winner/finalist status ("Certificate verified" / "Application on record"); unknown tokens 404.

  • Migration 016: communications.channel gains sms.

Fixed

  • Core\Search uses distinct PDO placeholders per occurrence (native prepares reject

repeated named params).

  • QA: duplicates-page assertion is now data-volume-proof (24k+ accumulated demo alerts).

[1.7.0] β€” 2026-08-30

Photo flexibility (Pakistan UX).

Added

  • Photo upload may be deferred to dashboard (POST /dashboard/photo) for award/goh; forms show hint β€œOptional now β€” you can upload later”.
  • Dashboard member page shows upload/change-photo form when photo missing, with β€œPhoto required before approval” CTA.

Changed

  • FR-0304/FR-0305/FR-0308 + UC-04/UC-05: photo optional at signup, required before approved.
  • views/register/award.php:41 + views/register/goh.php:21 no longer required on file input.

Fixed

  • Single approval and bulk approve now block when photo missing (redirect ?err=nophoto / ?skipped=N), audited as registration.approve_blocked_no_photo.

[1.6.0] β€” 2026-08-29

Scale & polish.

Added

  • Maintenance mode (FR-0106): site.maintenance toggle + message in /admin/toggles;

public returns 503 with Retry-After, admin/staff bypass.

  • Bulk ops (FR-1604): approve/reject multiple registrations from /admin/registrations

(checkboxes + bulk action, per-row audit).

  • Audit CSV export (FR-1103): /admin/export?type=audit + button on audit log page.
  • System health (NFR-15): /admin/system β€” DB size, table rows/sizes, disk free/total,

storage size, queue depths (pending regs, open tickets, dup alerts), backup recency; linked in admin nav + overview.

Fixed

  • Toggles no longer clear when saving maintenance-only (preserves feature flags).
  • Vote rate-limit / bulk / 2FA edge cases hardened via QA.

[1.5.0] β€” 2026-08-29

Integrations & experience pack.

Added

  • Webhooks (FR-1601): /admin/webhooks CRUD β€” endpoint URLs, per-event subscriptions

(registration.created, vote.cast, winner.locked, duplicate.suspected, or *), HMAC-SHA256 X-FLA-Signature + X-FLA-Event headers, delivery log with HTTP status + fail counters, one-click test ping.

  • Venue leaderboard screen (FR-1602): /screen/{event-slug} β€” dark big-display theme,

30s auto-refresh, per-category top-5; public when leaderboard is public or event is finished, otherwise staff-only.

  • CSV exports (FR-1603): registrations + votes (event-scoped) from the admin area;

audited as export.csv.

  • Admin 2FA (TOTP) (FR-1604): per-account setup with secret + otpauth:// URI and

code confirmation; password β†’ code challenge at login; disable requires a live code; wrong codes audited (auth.2fa_failed).

  • PWA (NFR-13): branding-driven dynamic manifest (/manifest.webmanifest), generated

brand icons (bin/make-icons.php), service worker (/sw.js) β€” cache-first statics, network-first pages; installable on HTTPS.

  • QA: E2E phase 16 β€” 110 checks green (manifest/icons/SW, webhook create + live

delivery + signature + ping via local receiver, screen privacy→public, CSV + guards, 2FA enable→challenge→login→wrong-code→disable round-trip).

[1.4.0] β€” 2026-08-29

Public discovery & ops pack.

Added

  • Events directory /events (live + archive) and Hall of fame /hall-of-fame

(FR-0204, FR-1006) β€” linked in the main nav.

  • Printable certificates (FR-0605): print/PDF-ready HTML pages for winners & finalists

(/cards/{token}/{type}/print), brand-colored, print CSS, verification link.

  • Public read-only JSON API v1 (FR-1501): /api/v1/events,

/api/v1/events/{slug}, /categories, /winners β€” public data only, JSON 404s.

  • SEO surface (FR-1502): /robots.txt (admin areas disallowed, sitemap reference) +

dynamic /sitemap.xml including events & results pages.

  • Vote rate limiting (NFR-12): per-IP, default 30/hour, tunable via

voting.rate_limit_per_hour setting; audited as vote.rate_limited; friendly voter copy.

  • Automated backups (NFR-10): bin/backup.sh β€” single-transaction DB dump (gz) +

storage tarball, 14-day retention, cron-ready (see HELP.md).

  • QA: E2E phase 15 β€” 91 checks green (directory, hall of fame, robots/sitemap, API

incl. 404 JSON, printable certificate, rate-limit enforcement + restore, backup artifacts).

  • QA infra: suite lifts the per-IP vote limit during runs (single-host testing) and

restores the default on exit (trap).

[1.3.0] β€” 2026-08-29

Polish pack β€” closes the remaining feasible backlog (only owner-provided keys/policies remain).

Added

  • Email verification for basic (non-LinkedIn) signups (FR-0309/0310): token issued at

registration, /verify-email?token=… completes it (one-time), audit + notification; log-mailer keeps it mock-safe.

  • Vote-change policy per event (FR-0509): vote_change_allowed toggle on the event

form; voters can update their reason (one_per_candidate) or move their vote (one_per_event) β€” audited as vote.changed; default remains "votes are final".

  • Referral attribution (FR-0510): ?ref= on share links survives the OAuth round-trip

and lands on the vote row; applicants see a per-channel breakdown on their dashboard.

  • Finalist certificate cards (FR-0604): PNG card type finalist (text from

campaign.finalist_text setting) available on the applicant dashboard after results.

  • Security headers on every response (NFR-11): HSTS on HTTPS, X-Frame-Options

SAMEORIGIN, X-Content-Type-Options nosniff, Referrer-Policy, Permissions-Policy.

  • QA: E2E phase 14 β€” 76 checks green (headers, verify flow, referral record,

vote-change off/on behavior, no-dup-on-change, finalist card PNG).

[1.2.0] β€” 2026-08-29

Release-control completions β€” nothing functional left pending except owner-provided keys.

Added

  • Winner un-lock flow (FR-1005): /admin/winners locked categories can be unlocked β€”

snapshot removed (public results retract), winners revert to finalist, event returns to judging; re-lock replaces snapshot; fully audited (winner.unlocked).

  • Team password reset (FR-0705): admin re-issues temp passwords for judge/staff

accounts from /admin/judges; forced change on next login; old password invalidated; audited (team.password_reset).

  • QA: bin/qa-e2e.sh extended to 63 checks β€” unlock round-trip (snapshot removal,

status revert, public retraction, re-lock) + reset-pass flow (re-issue, forced change, old-password rejection).

[1.1.0] β€” 2026-08-29

Duplicate-candidate detection (BR-011) + integrity fixes.

Added

  • Core\Duplicates matcher: signals = email, LinkedIn, CNIC hash (salted SHA-256 β€”

plain CNIC never stored), name+WhatsApp pair, same-account repeat-application across events; weighted scoring.

  • Scan runs on every registration path: public visitor/award/GoH, staff manual entry,

account claim-merge. Hits create duplicate_alerts rows, in-app notify all admins, audit duplicate.suspected.

  • CNIC field: mandatory on award form, optional on manual entry; format-validated

(13 digits); hash-only storage (users.cnic_hash).

  • Admin review queue /admin/duplicates: side-by-side comparison, **Confirm duplicate β†’

auto-veto the new registration, Dismiss**; duplicate.confirmed/dismissed audited.

  • Visibility: red ⚠ dup badge + "dupes" filter on /admin/registrations; open-alerts

KPI card on admin overview; sidebar link.

  • Docs: SRS FR-1300 + BR-011 + UC-17; new workflow docs/WORKFLOWS/duplicate-candidate.md.
  • QA: bin/qa-e2e.sh phase 12 (CNIC validation, repeat applicant, cross-account CNIC,

review UI, dismiss + confirm-veto) β€” 50/50 green; surface suite 33/33.

Fixed

  • Judges board no longer shows other judges' tallies pre-lock (anchoring, SRS A2).
  • Staff manual entry no longer binds to the staff/admin session account (was

attaching registrations to the operator's user row).

[1.0.0] β€” 2026-08-29

Full platform delivered in one drop (milestones v0.2.0–v1.0.0 merged per owner request), verified by a 41-check end-to-end suite (bin/qa-e2e.sh, 41/41) and the surface suite (bin/qa.sh, 33/33). Demo event content kept on the live site.

Added

  • Admin (FR-0100/UC-01): password login with IP throttling + forced password change,

Branding panel (name/logo/colors/campaign texts), feature-toggles UI, team accounts.

  • Events (FR-0200): CRUD + status lifecycle, categories show/hide/order, per-event

voting config (mode, 70/30 weights, reason toggle), public event pages.

  • Registration (FR-0300): visitor / award / guest-of-honor flows, LinkedIn OAuth

(mock-capable), WhatsApp requirement (BR-007), photo uploads, one-registration rule (BR-001/002), staff manual entry + owner claim-by-email, success pages with share links + "I have applied/visiting" cards.

  • AI interviews (FR-0400/UC-08): admin question bank per category, randomized 5-question

sessions (BR-010), AI marking (score+feedback via gateway; mock deterministic), session totals shown to applicant, judges and admin.

  • Public voting (FR-0500/UC-07): candidate share pages with Open Graph + share-card

previews, LinkedIn-verified voters (mock binds identity to browser session), restriction modes (BR-003), optional voter reason, duplicate protection, own-voters visibility (BR-006), vote notifications.

  • Judges (FR-0700/UC-10): team account creation (temp password + forced change),

event/category assignments, review board, approve/disapprove/needs-info, recusal.

  • Dashboards (FR-0800/UC-16): admin KPIs with 7-day trends, Chart.js graphs

(votes-over-time, funnel, category shares, AI distribution), audit tab with filters (FR-0807/1102), AI insights panel with anomaly flags (FR-0407), staff desk, applicant dashboard (votes chart, voters, AI transcript, cards, tickets, notifications).

  • Tickets (FR-0901): threaded tickets with staff queue, statuses, notifications.
  • Mass comms (FR-0902–0904): audience segments, preview, per-recipient delivery

tracking; email log transport + simulated WhatsApp (keys plug in later).

  • Winners (FR-1000/UC-11): 70/30 blended rankings + judges tallies, veto/unveto

(BR-005), approve-&-lock snapshots (BR-009), auto-finish, public results page, winner badge cards (PNG).

  • Platform core: CSRF on every form, role guards, audit writer on all sensitive

actions, media streaming route, on-demand GD card service, log Mailer, notify queue.

  • QA: bin/qa.sh (33 surface checks) + bin/qa-e2e.sh (41-check full lifecycle).

Fixed

  • move_uploaded_file failures: storage/ must be owned by the php-fpm user

(documented in HELP.md); uploads/cards/logs/cache are www-data-writable.

  • Mock LinkedIn identity is now stable per browser session (true duplicate-vote testing).

Security

  • All POST endpoints enforce CSRF tokens; team logins throttled (5 fails/10 min/IP)

with audit entries; upload validation (type/size/dimensions); forced initial password change for created accounts; winner announces happen only via audit-locked snapshots.

Pre-1.0 QA fixes (delivered as commits after v0.1.0)

Added

  • SRS: admin dashboard spec expanded β€” graphs (FR-0805: votes-over-time, funnel,

category shares, top candidates, AI score distribution), full stats coverage (FR-0806, 7d/30d trend KPIs), audit tab (FR-0807), AI insights (FR-0407: weekly summaries, judge-ready applicant briefs, vote-anomaly flags), new use case UC-16, chart-stack decision D11 (Chart.js CDN + JSON endpoints). Roadmap updated: v0.2.0 gets a dashboard starter, completion in v0.8.0.

  • bin/qa.sh β€” repeatable QA suite (29 checks: route statuses + body markers for every

public page and all docs, security probes .env/.git/path traversal, HTTPS redirect). Usage: ./bin/qa.sh [base-url].

Fixed

  • Docs viewer fatal error (sort($paths ??= []) β€” expression cannot be passed by

reference); /docs, /docs/srs and all workflow pages restored.

  • Doc slugs are canonical: docs/SRS.md β†’ /docs/srs, workflows β†’ /docs/workflows~<name>;

registration placeholder cross-links updated accordingly.

  • QA script: grep marker handling (-- guard) and correct HTTPβ†’HTTPS redirect probe.

Security

  • database/migrate.sh no longer carries a default DB password β€” credentials come from

.env / environment, script refuses to run without them.

  • Production posture: APP_DEBUG=false in live .env β€” stack traces are no longer

rendered to visitors (errors go to logs only).

[0.1.0] β€” 2026-08-29

Docs + scaffold baseline.

Added

  • Plain-PHP modular core: Router, DB (PDO/MariaDB), Settings, View, Config,

Env, Auth session skeleton, helpers (url(), setting(), absolute_url()…).

  • Module skeletons under src/Modules/: Home, Docs, Registration, Branding, Events,

Ai, Voting, Cards, Judges, Tickets, Comms, Winners, Audit, Notifications, Dashboard.

  • Docs viewer (/docs, /docs/{slug}): renders all repo .md files as organized

webpages with TOC + mermaid support (FR-0103).

  • Core services:
    • AI gateway β€” MiniMax platform driver (minimax-m3, OpenAI-compatible) + deterministic

MockProvider; mock active until keys (NFR-08).

    • LinkedIn OIDC client + mock fixture.
    • WhatsApp null driver (logs to storage/logs/whatsapp.log).
    • Share-card generator: GD driver (applied / visiting / winner, 1080Γ—1080 PNG);

AI driver reserved (cards.driver).

  • Landing page (responsive, DB-branded), 404 page, registration placeholder pages.
  • Database migrations 001–009: settings(+seeds), users, events, categories,

registrations, question_bank, ai_sessions, ai_answers, votes, judge_assignments, judge_reviews, tickets, ticket_messages, feature_toggles(+seeds), audit_log, notifications, communications, communication_recipients. Runner: database/migrate.sh.

  • Documentation set: docs/SRS.md (business use-cases UC-01…15, FR/NFR, BR-001…010),

7 workflow docs (docs/WORKFLOWS/), TODO.md (session handoff), ROADMAP.md, WORKFLOW.md, HELP.md, AGENTS.md.

  • .env.example, .gitignore, composer.json (PSR-4 autoload only β€” no framework),

bin/card-demo.php, Apache-inline public .htaccess.

  • Live deployment: nginx vhost (deploy/nginx.conf template) serving

https://event.production1.jugaar.ai on plain 443 — Let's Encrypt certificate issued, HTTP→HTTPS redirect enabled. Verified public responses on /, /health, /docs/*.

Fixed

  • Router treats HEAD as GET (monitoring tools send HEAD).

Security

  • .env git-ignored; PDO prepared statements; session cookie flags

(HttpOnly, SameSite=Lax, Secure on HTTPS).