Changelog

[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).