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(expliciterror_logpath). - 404s logged with method + path in Router.php dispatch.
- 403s logged with method + URI + user in Controller.php role guard.
session.gc_maxlifetimeset to 604800 (7 days) to match cookie lifetime β
- PHP errors now write to
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.shcompresses logs older than 1 day, deletes
compressed logs older than 7 days. Suggested cron: 0 0 * * *.
- Orphan upload cleanup: new
bin/cleanup-orphan-uploads.phpremoves 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.confupdated forfla.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_30dandvisitors_7dalready in KPI grid;
analytics page provides deeper drill-down.
[5.5.0] β 2026-09-11 β Winners carousel, form-based voting, applicant questionnaire
- 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_banknow hastype(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_agentcolumn onaudit_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/votesheld-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-inkso print buttons on
gold brands get dark text instead of white-on-gold.
qa.shlanding 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/homeand/judgeas 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).
- storage tarball +
[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-2of 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 .btnblanket white rule
now excludes .btn-gold (dark text restored).
- Light-mode
--muted4.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.sh37/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 -lclean; stagingqa.sh37/37;qa-e2e.sh284/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 tagv5.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/compare500 βu.photo_pathreferenced a nonexistent column.
High
- Live-vote JSON endpoint bypassed the
leaderboard_publicgate (BR-006). - Hall of Fame + Alumni spoiled ceremony-mode winners (bypassed
reveal_mode). /admin/backupshelled 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/fitunauthenticated 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_atSept 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.shnow fully target-DB-aware viaQA_DB/QADBβ no hardcodedfla_eventwrites- 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 usesauto-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-2grid-3) now userepeat(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-herocard 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-lieverywhere (login, visitor, award, GoH, voter verification). - Members see
li-verified-chipwhen linked;/auth/linkedin?intent=linkattaches 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-libuttons 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=linklets 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+websiteclaims (best-effort, no schema change). - Onboarding +
/admin/systemKPI 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
.envsource (MAIL_FROM_NAME=FLA Testunquoted,
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-2026shows
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:-240pxextended 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=auditchunked export crashed (ANDglued onto a
WHERE-less SQL) β correct WHERE/AND now.
/admin/votesempty-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:
recurrenceweekly/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).
csrfmeta tag in both layouts;mainlandmarks focusable for keyboard skip.
Accessibility
- Every
<img>now carriesalt(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/votesbrowse/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}.icsper 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:
jobstable +bin/queue.phpworker (cron* * * * *), retries Γ3
with backoff, handlers: telegram.send, mail.send, card.render.
- Structured login log:
login_attemptstable (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_openevent 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-scoringtransparency hub β public explanation of the 70/30 engine, admin veto, SHA-256 hash-chain verification and winner-lock metadata./surveypost-event feedback β 1β5 ratings + comment for attendees of the latest finished event; answers stored insurvey_answers./badgesprintable 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/setupmedia 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(firesbin/backup.sh). - Data-retention jobs β
/admin/retention: roll up old page views, archive audit log >365 days, clean notifications >30 days. Migrations030_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/docsviewer
("Specification" group) that links to the visual page.
qa.sh+2 checks (how-it-works page, overview doc) β 35 checks.
Fixed
qa-e2e.shpipefail flake βcurl | grep -qunderset -o pipefailraces: 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\Authin 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.shno 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-visiblering, 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_atthrough 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}.pngpath (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
gotocan'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
/healthreadsapp.versionfrom 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
vetoedBEFORE any
status UPDATE (was dead code after the UPDATE in bulk; absent in single).
- FR-0705 enforced:
must_change_passwordblocks 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_tokensecret
(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_presentersjoin 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 (
:oused 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
Uploadimport 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::persontypedintparam 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.channelgainssms.
Fixed
Core\Searchuses 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) foraward/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 beforeapproved.views/register/award.php:41+views/register/goh.php:21no longerrequiredon file input.
Fixed
- Single approval and bulk approve now block when photo missing (redirect
?err=nophoto/?skipped=N), audited asregistration.approve_blocked_no_photo.
[1.6.0] β 2026-08-29
Scale & polish.
Added
- Maintenance mode (FR-0106):
site.maintenancetoggle + 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/webhooksCRUD β 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_allowedtoggle 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/winnerslocked 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.shextended 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\Duplicatesmatcher: 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.shphase 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_filefailures: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.shno longer carries a default DB password β credentials come from
.env / environment, script refuses to run without them.
- Production posture:
APP_DEBUG=falsein 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.mdfiles as organized
webpages with TOC + mermaid support (FR-0103).
- Core services:
- AI gateway β MiniMax platform driver (
minimax-m3, OpenAI-compatible) + deterministic
- AI gateway β MiniMax platform driver (
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.conftemplate) 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
HEADasGET(monitoring tools send HEAD).
Security
.envgit-ignored; PDO prepared statements; session cookie flags
(HttpOnly, SameSite=Lax, Secure on HTTPS).
