Examulator — Changelog
Consolidated release history for the Examulator solution (both product generations, all supporting projects). This file supersedes the previous per-generation Examulator/CHANGELOG.md and Examulator3/CHANGELOG.md files (which were maintained as byte-identical mirrors and are removed as of 26231.01-closed).
Entries are ordered newest-first. Each release heading carries a scope line naming the components touched by that release; individual subsections may narrow that scope further via a project-list tag under the subsection heading.
Product components at a glance
Gen2 (com.certificationtoolanddie.examulator) — Examulator (PC/web) · ExamulatorCore (shared logic) · ExamulatorMaui (Android) · ExamulatorApi (Contribute Results endpoint) · ExamulatorDiag · ExamulatorTrace · ExamulatorUpdater · ExamulatorDiagnostics · ExamulatorTests · ExamulatorIrt (shared with Gen3)
Gen3 (com.certificationtoolanddie.examulator3) — Examulator3 · Examulator3Core · Examulator3Maui · Examulator3Api · Examulator3Diag · Examulator3Trace · Examulator3Diagnostics · Examulator3Tests
The four surfaces referenced by many entries are: PC Gen2 (Examulator), PC Gen3 (Examulator3), MAUI Gen2 (ExamulatorMaui), MAUI Gen3 (Examulator3Maui).
[26286.01] — 2026-10-13 (current)
Scope: Quiz domain balance — Domain 0 excluded from quizzes and multi-domain quizzes allocated by blueprint weight. ExamulatorCore · Examulator3Core (selection) · Help on all four surfaces. versionCode → 2628601.
Android release following a user report that CISA quizzes under-represented Governance. Investigation confirmed the CISA Domains.percentage values and the Linear allocation were correct; the quiz path was not blueprint-aware.
Fix — "All Domains" quizzes included Domain 0 (unscored pretest) items
ExamulatorCore · Examulator3Core
"All Domains" sends no domain filter, so the quiz query drew from every domain — including Domain 0, which holds the unscored/beta pretest items reserved for CAT. Those items (≈7.6% of the CISA pool) were served and scored in quizzes and diluted every real domain's share (Governance fell to ~16.7% against an 18% weight). GetQuizQuestionsAsync and CountForQuizAsync now exclude Domain 0 explicitly. The quiz domain pickers already hid Domain 0, so single-domain and hand-picked selections were never affected; Linear and CAT are unchanged.
Change — quizzes now follow the exam-outline domain weights
ExamulatorCore · Examulator3Core
Quizzes previously drew one random sample from the eligible pool, so the domain mix tracked how the question bank happened to be built rather than the exam outline — fine for a balanced pool like CISA, but a domain holding 21% of a pool while weighted 10% would be over-served roughly two-to-one. When more than one scored domain is in play ("All Domains" or a multi-domain selection), the requested count is now allocated across the selected domains by their Domains.percentage weights, renormalised to the selection, using the same largest-remainder (Hamilton/Hare) method as Linear:
All Domains: exact blueprint split (a 50-question quiz gives a 10% domain 5).
Subset: weights rescaled to 100% over the chosen domains (20% + 25% → 44.4% / 55.6% → 22 / 28 of 50).
Each selected domain gets at least one question when the count allows.
Questions are random within each domain's quota, then shuffled together.
If a domain can't fill its quota (small pool, difficulty or history filters), the shortfall is topped up at random from the other selected domains, so the quiz reaches the requested length whenever enough eligible questions exist.
Single-domain quizzes, and exams with no usable weights, keep the plain random draw. The Help pages (Quiz Configuration on PC; Quiz mode on MAUI) describe the new split.
Fix — Domains.percentage values written with a "%" were read as zero
ExamulatorCore · Examulator3Core
ParsePct used NumberStyles.Any, which does not accept a percent sign, so a value stored as 18% parsed as 0 and that domain was silently dropped from Linear (and now quiz) allocation. The parser now trims whitespace and a trailing % first. Current pools store bare numbers, so no shipped exam was affected.
[26284.01] — 2026-10-11
Scope: Fix the Windows "file being used by another process" failure when installing a question-pool update. ExamulatorCore · Examulator3Core (installer). versionCode → 2628401.
Fix — "Install Update" failed on Windows with "the process cannot access the file … because it is being used by another process"
ExamulatorCore · Examulator3Core
Installing or updating a pool could fail at the final save step with a file-in-use error (e.g. Could not save database: … 'CCSP.db' … being used by another process). The exam databases are opened ReadOnly with a shared cache and a 128 MB memory-map, and Microsoft.Data.Sqlite pools connections by default, so a disposed read connection (from the version check, the query repositories, or the Select-Exam "Pool Version" display) keeps the Windows file handle and the memory-map open — blocking the installer's delete/overwrite of the existing .db. The installer now releases pooled handles (SqliteConnection.ClearAllPools()
GC) before the overwrite and retries the delete/move briefly to ride out a concurrent page render or an antivirus/search-indexer touch. Pooling stays on for normal reads (it matters for the memory-mapped access to the large pools); it is only cleared at install time.
Note — pool user_version must match the release. The app decides whether a newer pool exists by comparing the release/manifest version against each database's PRAGMA user_version, which is stamped by the pool-build pipeline (the app only reads it). A pool published as a new version but still carrying an older user_version will show a perpetual "update available" banner and the wrong Pool Version. Stamp each pool to its release version at build time (PRAGMA user_version = <YYDDD>;); the stamp_pool_version.py helper can check or re-stamp an existing set. Updated the CI/CD pipeline to automatically re-stamp the database files upon build. Previously the pipeline only re-stamped the internal version number if there was an actual database change, which let to a mismatch between the internal database version and the external file name version.
[26283.01] — 2026-10-10 (current)
Scope: ISACA quiz scaling fix; Select-Exam installed-version display and long-press update. All four surfaces (scoring + PC cards); ExamulatorMaui · Examulator3Maui (long-press).
Android release rolling up the Select-Exam enhancements and the ISACA quiz-scoring fix made since 26281.03. versionCode → 2628301.
Fix — ISACA quiz reported a scaled score above its own maximum (e.g. 920/800)
ExamulatorCore · Examulator3Core (scoring) · all result surfaces
Quiz finalisation computed the score as raw-ratio × 1000 and used the ISC2 pass line (700) for every agency, while the results screen displayed the agency maximum — so an ISACA quiz (e.g. 23/25 on CISA) showed 920 / 800. Quiz now maps the raw ratio through the exam's agency profile (AgencyScoringProfile.RatioToScaled) exactly as Linear does: ISC2 is a straight ×1000 and is unchanged, while ISACA maps onto 200–800 with the pass line at 450 (70% raw). 23/25 on an ISACA quiz now reads ~707/800, and the quiz pass/fail verdict uses the ISACA 450 line instead of passing at ~45%. The "How this score was computed" note on the results and historical-results screens, and the Help scoring tables, were made agency-aware to match (they no longer assert a flat 0–1000 for every exam). Already-recorded results keep their stored score; this corrects newly completed quizzes.
Add — installed pool version on the Select Exam cards
Examulator · Examulator3 · ExamulatorMaui · Examulator3Maui
Installed exam cards now show the installed question-pool version (the pool's PRAGMA user_version): MAUI renders it inline on the badge (✓ Installed · v26280); the PC cards add a "Pool Version" stat alongside Questions / Time Limit / Modes.
Add — long-press an installed exam to update its pool (MAUI)
ExamulatorMaui · Examulator3Maui
When the manifest reports a newer pool for an installed exam, the card shows an "⬆ Update available" badge; long-pressing (the touch contextmenu gesture) downloads and installs the update in-app through the same DatabaseDownloadService path as a fresh install, with inline progress. A normal tap still starts the exam. The PC surfaces already expose an explicit "Install Update" button, so they were left unchanged.
[26281.03] — 2026-10-08
Scope: Android app icon, in-app Play updates, and update-dialog download fix. ExamulatorMaui · Examulator3Maui.
Android-focused release over 26281.02. Restores the real app icon, adds Google Play in-app updates, and fixes the launch "Question Bank Updates" dialog so it installs updates in-app instead of bouncing the user to a Google sign-in page. versionCode bumped to 2628103 for the Play upload.
Fix — app icon reverted to a generic "E"
ExamulatorMaui · Examulator3Maui
The MAUI app icon had been pointing at placeholder appicon.svg / appiconfg.svg files that drew a plain gold "E", so production builds shipped with a generic icon instead of the Examulator logo. Re-cut the icon from the real brand art as the gear/shield badge (company text removed, sized inside the Android adaptive safe zone) on the navy brand background, and repointed MauiIcon at the PNG sources. Opaque base (no alpha) keeps the iOS icon set valid; transparent-corner foreground drives the Android adaptive icon. The full logo-with-text is retained as source art outside the icon pipeline.
Fix — "Question Bank Updates" dialog failed with a Google login / "unsupported file type"
ExamulatorMaui · Examulator3Maui
The launch update dialog offered each pool update as an external hyperlink (<a href target="_blank">) to the manifest's Google Drive URL. In the MAUI WebView that is a navigation, so Google served a sign-in/consent page and the app received HTML rather than the .db ZIP — surfacing as "This platform does not support this file type." The dialog now downloads through the same in-app DatabaseDownloadService the Select Exam screen uses (HttpClient + Drive-URL rewrite + ZIP extraction), with inline progress and a success/failure banner. No WebView navigation, no Google login. The Select Exam path and the PC update banner were already correct and are unchanged.
Add — Google Play In-App Updates (flexible flow)
ExamulatorMaui · Examulator3Maui
Added Platforms/Android/InAppUpdateService.cs, wired into MainActivity (OnResume), surfacing Play's flexible in-app update prompt and completing a background-downloaded update on return to the app. Android/Play only — no-ops for sideloaded copies; iOS and the PC builds are unaffected. New Android-only package Xamarin.Google.Android.Play.App.Update.
Fix — R8 dex failure after adding the Play update package
ExamulatorMaui · Examulator3Maui
The Play App Update package pulled AndroidX Fragment up to 1.9.0, where the Kotlin-extension FragmentKt class was merged into the main fragment artifact. With Xamarin.AndroidX.Fragment.Ktx still at 1.8.x, FragmentKt existed in both AARs and R8/D8 failed with "androidx.fragment.app.FragmentKt is defined multiple times." Pinned both Xamarin.AndroidX.Fragment and Xamarin.AndroidX.Fragment.Ktx to 1.9.0 (where fragment-ktx is an empty forwarding stub) to resolve the duplicate.
[26281.02] — 2026-10-08
Scope: In-app Help — new Support section across all four surfaces. Examulator · Examulator3 · ExamulatorMaui · Examulator3Maui.
Content-only patch over 26281.01 (which went to the Android push). No engine, scoring, or data changes — the revision was bumped so the new Android build carries a higher versionCode (2628102) than the 26281.01 upload.
Add — "Support" section on the Help page
Examulator · Examulator3 · ExamulatorMaui · Examulator3Maui
Added a dedicated Support section (now §2, directly after Getting Started) to the Help page on all four surfaces, listing the three ways to reach us:
Reddit DM to u/mikedn02908
the support subreddit r/ctdltd
email support@certificationtoolanddie.com
…plus a reminder to include screenshots, reproduction steps, and any other relevant material when reporting a bug so issues can be diagnosed quickly. Subsequent Help sections were renumbered 3–12 (the PC quick-jump table of contents gained a Support link, and the internal §-cross-references for CAT & IRT and Configuration were updated to match).
Change — consolidated the PC bug-report FAQ into Support
Examulator · Examulator3
Removed the standalone "How do I report a bug or suggest a feature?" FAQ item from the PC surfaces; its one unique detail — for exam-result bugs, include the ExamulatorDiag export — now lives in the Support section's bug-reporting note, cross-linked to §9 Diagnostic Tools. (The MAUI Help FAQ never carried this item, and Android has no ExamulatorDiag tool, so the MAUI Support section stands on its own.)
[26281.01] — 2026-10-08
Scope: Android import + Play-Store optimization fixes; rebrand completion. ExamulatorMaui · Examulator3Maui (+ shared rebrand across all surfaces).
Patch over the first production build (26280.01), addressing a user-reported Android import failure and the Play Console DEX-optimization advisory.
Fix — "File picker error: This platform does not support this file type" (Android import)
ExamulatorMaui · Examulator3Maui
Importing an exam database from device storage ("Import from file") threw "File picker error: This platform does not support this file type" and never opened the picker. Cause: the MAUI FilePicker was given an Android FilePickerFileType of */*; a .db has no registered Android MIME type, so SAF's type resolution throws (and a filter greys out .db files on some file managers anyway). Fixed by removing the file-type filter entirely — the picker opens to all files and the existing post-pick validation (must be .db, a known exam code, and a valid SQLite header) gates the import. Both MAUI gens.
Fix — ISACA exams rejected on import
ExamulatorMaui · Examulator3Maui
The import allow-list predated ISACA support, so importing an ISACA .db (CISA, CDPSE, CCOA, CCA, CCP, AAIA, AAIR, AAISM) was rejected as "not a recognised exam database". Both gens now derive the ISACA half of the allow-list from LegacyExamAgencyMap.IsacaCodes — the same source of truth the agency tabs and scoring use — so it can't drift again. The Gen2 rejection message is now agency-neutral.
Build — opt-in R8 obfuscation for the Play DEX-optimization advisory
ExamulatorMaui · Examulator3Maui
Added <AndroidR8ObfuscationMode>private-members</AndroidR8ObfuscationMode> (Android only). .NET for Android ships R8 with obfuscation off, which is why Google Play reports ~1% (its 25%-by-Feb-2027 threshold). private-members renames private/package-private members only — public + JNI-callable names stay intact — raising the metric without risking the interop bridge. Requires a .NET 10.0.1xx servicing SDK (or .NET 11, where it is the default); ignored on older SDKs. Verify on an internal-testing track before production.
[26280.01] — 2026-10-07
Scope: First production build. ISACA agency support, four-band difficulty standardization, and the ISC2 rebrand across all four surfaces + shared ExamulatorIrt; PC Help and exam-download fixes.
First production (General Availability) build, promoted from the -rc1 Early-Access line. Per app-store convention the production channel carries no suffix — the pre-release tags (-alpha, -closed, -rc1) are dropped and the version ships as the bare 26280.01. It bundles everything landed since the 26274 release candidate: ISACA question-pool support, the ISC2 rebrand, the four-band difficulty standardization, and the PC Help/download fixes below.
Feature — ISACA question-pool support (per-agency scoring)
ExamulatorIrt · ExamulatorCore · Examulator3Core · Examulator · Examulator3 · ExamulatorMaui · Examulator3Maui
Gen2 can now surface and score ISACA exams (CISM, CISA, CRISC, CDPSE, CGEIT, CCOA, AAIA, AAIR, AAISM) alongside its ISC2 pools, with the scoring engine also wired into Gen3 so both generations stay aligned.
Per-agency scoring profiles — new AgencyScoringProfile / ScoringProfiles in the shared ExamulatorIrt library. The IRT θ-estimation engine is unchanged; only the result→scaled-score mapping, pass line, bar widths and difficulty-band labels vary per agency. The ISC2 profile reproduces the historical 0–1000 / 700 scoring exactly (ratio × 1000, symmetric θ slope), so nothing changes for existing exams.
ISACA scale — 200–800 with a 450 pass standard, anchored on the pass line and filled piecewise (ISACA does not publish its scaling, so this is an acknowledged approximation; results and reports show a disclaimer). Four difficulty bands over the raw 1–253 scale (Easy < 64, Moderate 64–127, Difficult 128–190, Expert 191–253). Linear-only — ISACA has no CAT, which the existing MaxQuestions == MinQuestions gate suppresses automatically; overall pass is the overall scaled score, with per-domain green/red at 450 (consistent with the ISC2 breakdown behaviour).
Agency classification — new LegacyExamAgencyMap in each Core project maps the ISACA exam codes to the agency (Gen2 has no agency column); Gen3 uses its real AgencyCode and keeps the same map as a fallback.
Live scoring, results & reports — Linear finalization, the live/historical Results pages, PDF export, and the score-progression / readiness / domain reports all now express scaled score, pass line and bar widths on the exam's agency scale, across all four surfaces.
Tabbed Select Exam — the Gen2 Select-Exam screen (PC + MAUI) gains an ISC² / ISACA tab row (shown only when more than one agency is present; ISC² default). Gen3 already had agency tabs.
Tests — ScoringProfileTests (both test projects) cover the ISC2 legacy-equivalence invariants, the ISACA piecewise mapping and band edges, agency lookup, and the Gen2 code→agency classification.
ISACA Exam Gen 2 Question Pools published for download — All original questions generated via Fable/Opus pipeline. AAIA: 210,704; AAIR: 263,194; AAISM: 264,235; CCOA: 263,648; CDPSE: 264,977; CGEIT: 265,257; CISA: 272,212; CISM: 292,790; CRISC: 284,121
The Help pages on all four surfaces describe the ISACA 200–800 / 450 model alongside ISC2.
Difficulty bands standardized to four across every agency. ISC2 publishes no difficulty labels and its previous five-band split (…Advanced…) was Examulator's own invention, so both agencies now use ISACA's four bands over the raw 1–253 scale — Easy (1–63), Moderate (64–127), Difficult (128–190), Expert (191+). This changes only presentation and the quiz difficulty filter (now four options); raw difficulty values and IRT scoring are untouched. DifficultyLevel (both Core projects), the shared band table in ScoringProfiles, the difficulty-balanced Linear allocator, and all difficulty reports/breakdowns were updated together.
Fix — Help sections not expanding (PC)
Examulator · Examulator3
The collapsible Help sections had the CSS (.help-section.collapsed) and the chevron affordance but no script to toggle them, so clicking a header did nothing. Added the toggle script (click + keyboard) to both PC Help pages. The MAUI Help pages already toggled via a Blazor @onclick handler and were unaffected.
Fix — exam-database download buttons missing on PC
Examulator · Examulator3
The Select-Exam page read the version-manifest result immediately after a fire-and-forget start, so on the first (and only) server render the fetch had usually not finished and every DownloadUrl was null — no Install buttons for any exam. The page now awaits the manifest check before building the card list (memoized, 8s timeout), so install/update buttons appear on first load. The MAUI pages already polled and re-rendered.
[26274.01-rc1] — 2026-10-01
Scope: iOS (both MAUI gens).
iOS 27 launch-crash fix, plus assorted iOS App Store packaging fixes discovered while shipping the first iOS build (the TestFlight release candidate). Revision bumped so the build number exceeds the earlier (faulty) App Store Connect upload.
Fix — iOS 27 crash on launch (UIScene lifecycle not adopted)
ExamulatorMaui · Examulator3Maui
First iOS release crashed immediately on launch on iPhone/iPad running iOS 27 (EXC_BREAKPOINT / SIGTRAP in _UIApplicationEvaluateRuntimeIssueForNoScene LifecycleAdoption), while running fine on iOS/iPadOS 26. Apps linked against the iOS 27 SDK (Xcode 27) must adopt the UIScene lifecycle; iOS 26 only warned, which is why dev testing on an iPadOS 26 device missed it. Fixed per the official MAUI template remedy (dotnet/maui #38601): added UIApplicationSceneManifest to Platforms/iOS/Info.plist (single window scene, config name __MAUI_DEFAULT_SCENE_CONFIGURATION__, delegate SceneDelegate) and a Platforms/iOS/SceneDelegate.cs ([Register("SceneDelegate")] : MauiUISceneDelegate). Both gens.
Fix — iOS App Store packaging (icons, version string, splash)
ExamulatorMaui · Examulator3Maui
Resolved the issues that blocked the first App Store upload: - App icons (90022/90023/90713): hand-written Platforms/iOS/Info.plist doesn't receive actool's auto-merged icon keys, so added CFBundleIconName (appicon) + CFBundleIcons/~ipad explicitly; MauiIcon switched to the flat SVG source. - Version string: CFBundleShortVersionString must be 1–3 integers, so an iOS-only ApplicationDisplayVersion override emits Apple-format YYDDD.V (e.g. 26274.1) while Android keeps the full YYDDD.VV-channel scheme. - Splash: removed <text> from splash.svg (Resizetizer/SkiaSharp crash on SVG text). - Linker: SupportedOSPlatformVersion values made inline (a newline in the value broke custom-linker-options.txt → MT2301). - Signing: manual iOS code-signing block pinned in the csproj.
Infrastructure — Version bumped to 26274.01-rc1 (13 sites)
Examulator · ExamulatorMaui · Examulator3 · Examulator3Maui · ExamulatorDiag · Examulator3Diag · ExamulatorTrace · Examulator3Trace
Julian date bump 26272 → 26274 (2026-10-01). Channel unchanged (rc1). MAUI <ApplicationVersion> = 2627401. 13th site: the iOS-only ApplicationDisplayVersion override (Apple format) is now part of the bump — 26274.1. Bumped specifically so CFBundleVersion exceeds the earlier faulty App Store Connect upload (Apple rejects duplicate/lower build numbers).
[26272.01-rc1] — 2026-09-29
Scope: Both generations, all four surfaces (except where noted).
Bug-fix and feature release. Fixes two report defects reported by a CCSP user, completes the CPE report on all surfaces, adds an in-app release-notes viewer, and lands iOS build/signing configuration.
Fix — CAT Ability Trajectory (and all mode-filtered reports) showed no CAT sessions
ExamulatorCore · Examulator3Core
ExamMode.CAT.ToString() persists the mode as "CAT" (all caps), but ReportDataService.ModeAllowed matched it case-sensitively against "Cat" — so "CAT" != "Cat" and every CAT session was silently excluded from any mode-filtered report's session picker. This made CAT Ability Trajectory (a CAT-only report) show zero eligible sessions for every user since it shipped, and also under-counted CAT sessions toward other reports' eligibility. Linear and Quiz were unaffected (their enum names match their stored strings). Fixed by making ModeAllowed case-insensitive in both gens.
Fix — Reports wrongly reported "not enough data" for CAT-only users
ExamulatorCore · Examulator3Core
A second instance of the same "CAT"-vs-"Cat" casing defect, in the eligibility path. GetExamSummariesAsync computes each exam's Linear/CAT session and question counts with SQL CASE WHEN mode IN ('Linear','Cat'), but the persisted value is 'CAT' and SQLite string comparison is case-sensitive — so CAT sessions were excluded from the counts. Those counts feed CoarseShortfall, the gate that decides whether a report has enough data, so a user with ample passed CAT exams (e.g. CCSP, a CAT-native cert) saw LinearCatSessions = 0 and every multi-session Linear/CAT report (Score Progression, Domain Trends, Readiness Snapshot, Persistent Gaps, etc.) incorrectly reported "not enough data." Fixed by making the summary query's mode comparisons case-insensitive (UPPER(mode) IN ('LINEAR','CAT')) in both gens. An exhaustive sweep confirmed no other case-sensitive mode-string comparisons remain (all other mode checks are enum-based).
Fix — Exam History kebab (⋮) menu rendered off-screen / behind rows
Examulator · Examulator3
The Exam History row-action menu (used for PDF export, delete, diagnostic export) is styled position: fixed (viewport-relative) but was positioned in JavaScript by adding window.scrollX/scrollY, which is only correct for position: absolute. On any scrolled page the menu was offset by the scroll amount and landed off-screen or behind other rows — only the bottom row's menu appeared usable. Fixed to use pure viewport coordinates from getBoundingClientRect(), with flip-up-when-near-bottom and left-edge clamping so the menu always stays on screen. Both PC gens. (MAUI is unaffected — it uses native action sheets.)
Fix — CPE Report on Android printed no recipient name
ExamulatorMaui · Examulator3Maui
The MAUI Configure page passed the recipient name via URL query (&recipient=), but the Output page never parsed it back, so _recipientName stayed empty and the certificate omitted the name. Added the missing query-parse in both MAUI Output pages.
Fix — CPE Report did not generate on Windows
Examulator · Examulator3
The PC Output view had a title-line reference to the CPE report but no render block for the report body — the else if (Model.Kind == ReportKind.CpeReport) section was never added, so generating a CPE report on the PC surface produced no output. Added the full certificate render block (masthead, certificate text, session table with totals, Download PDF button, disclaimer) to both PC gens; Gen3 includes the agency field its PDF handler requires.
Feature — In-app release-notes viewer on the update screens
ExamulatorCore · Examulator3Core · Examulator · Examulator3 · ExamulatorMaui · Examulator3Maui
New ReleaseNotesService fetches the published changelog page and shows "what's changed since your version" directly on the update-available screen, so users see what an update contains before installing rather than updating blindly. Extracts the page's <div role="main"> via balanced-tag matching and slices every release newer than the running version (versions are descending, newest first). MAUI shows it on the question-bank update dialog; PC shows it on the app-update banner as an expandable panel. Configurable via UpdateCheck:ReleaseNotesUrl. Every failure degrades to a "view online" link and never blocks the update. Notes: extraction runs against Google Sites HTML (no raw-markdown endpoint available there) and should be validated against the live page; PC injects the fetched fragment via MarkupString (source is the app's own controlled changelog).
Infrastructure — iOS build & signing configuration
ExamulatorMaui · Examulator3Maui
Manual iOS code-signing block added to ExamulatorMaui.csproj (ProvisioningType=manual, Apple Distribution key, com.certificationtoolanddie.examulator profile) so it persists across source drops. Removed earlier experimental MtouchLink/PublishTrimmed overrides that were causing (not fixing) an MT2301 linker error — the actual cause was Windows↔Mac toolchain version drift, resolved by matching workloads and clearing the PairToMac cache. Note: current .NET 10 iOS tooling requires Xcode 27 / Apple Silicon; Intel Mac build hosts are no longer supported by the toolchain.
Infrastructure — Version bumped to 26272.01-rc1 (12 sites)
Examulator · ExamulatorMaui · Examulator3 · Examulator3Maui · ExamulatorDiag · Examulator3Diag · ExamulatorTrace · Examulator3Trace
Julian date bump from 26258 to 26272 (2026-09-29). Channel unchanged (rc1). MAUI <ApplicationVersion> = 2627201 (26272 × 100 + 1).
[26258.02-rc1] — 2026-09-15
Scope: Both generations, all four surfaces.
Adds the CAT Ability Trajectory report (Tier 2), the CPE Report (Tier 4), a comprehensive theme system (12 named palettes on all four surfaces), three light-theme bug fixes, a SQLite crash fix, SHA-256 manifest verification, Android safe-area inset and tap-delay fixes, dropdown readability improvements, and updates the theme count to 13 palettes with the addition of this release's new colour schemes.
Fix — Theme selection had no visible effect (PC only)
Examulator · Examulator3
Selecting any theme (Light, Sepia, Paper, or the dark variants) under Accessibility → Visual Theme saved correctly but produced no visible change on the PC/web surface. Root cause: the base stylesheet set background-color and color on html, body together, reading --bg-root / --text-primary from :root. The theme attribute is emitted on <body>, so a theme's variable overrides reached body and its descendants but never html (body's parent) — the html element kept painting the default dark canvas over the themed body. Fixed by scoping the themed background-color/color to body alone and making html transparent, so the body-level theme override is the sole canvas colour. MAUI was unaffected (its html/body rules were already separated).
Feature — CAT Ability Trajectory (Tier 2)
ExamulatorCore · Examulator3Core · Examulator · Examulator3 · ExamulatorMaui · Examulator3Maui
New Tier 2 report that plots the CAT algorithm's ability estimate (theta, scaled 0–1000) and 95% confidence interval question-by-question across a single exam session. The 700 passing standard is marked with a dashed line; red/yellow/green zone shading shows when the estimate is below, at, or above the threshold. SE is computed from cumulative item information when IRT parameters are available; a proportional approximation (se_final × √(N/q)) is used otherwise. Registered as ReportKind.CatAbilityTrajectory = 10; CAT-only, single-session.
Feature — CPE Report (Tier 4)
ExamulatorCore · Examulator3Core · Examulator · Examulator3 · ExamulatorMaui · Examulator3Maui
New Tier 4 report that generates a formal PDF certificate suitable for CPE credit submissions to ISC², ISACA, or similar bodies. The user selects sessions for one exam and enters the recipient name; the report tallies total study hours and minutes, prints a certificate block, and appends a per-session detail table (start/end date-times, mode, question count, duration). ReportPdfExportService.BuildCpeAsync bypasses the generic BuildAsync dispatch to carry the recipient name. All four surfaces implemented.
Feature — Theme system (12 named palettes)
Examulator · Examulator3 · ExamulatorMaui · Examulator3Maui
Twelve named colour palettes added to the Accessibility settings dropdown on all four surfaces: Midnight (OLED black), Slate (cool gray), Warm Dark (charcoal), Nord (arctic blue, frost-teal accent), Solarized Dark (teal bg, yellow accent), Amethyst (purple accent), Catppuccin Mocha (mauve accent), Green Terminal (phosphor green on black), Light (cream), Sepia (warm cream), Paper (pure white), and High Contrast (existing). Each palette overrides all relevant CSS custom properties, including tokens that were missing from the original light theme — --bg-root, --bg-nav, --text on MAUI, and all six gold derivative rgba values — so none of the readability failures that affected the original light theme can recur.
Fix — Light theme readability (3 bugs)
Examulator · Examulator3 · ExamulatorMaui · Examulator3Maui
Three defects in the existing light accessibility theme corrected across all four CSS files: (1) --bg-root (#07111f dark navy) and --bg-nav (#050e1a near-black) were never overridden — the page canvas and nav bar stayed dark while text flipped to #1a1a1a, producing dark text on a dark background; (2) --gold-subtle, --gold-border, and --gold-glow were hardcoded as rgba(201,162,39,...) dark-mode values and not overridden, affecting 48 CSS rules; (3) MAUI --text (#C8C8C8 light-gray) was not overridden, producing near-invisible text on the cream background.
Fix — Dropdown readability
Examulator · Examulator3 · ExamulatorMaui · Examulator3Maui
<select option> elements take their background and color from the OS UA stylesheet and ignore styles applied to the parent <select>. Added explicit background-color and color overrides with !important on all four CSS files so the app theme is honoured inside dropdown lists in every theme including light and sepia.
Fix — SQLite crash in CountForQuizAsync
ExamulatorCore · Examulator3Core
QuestionRepository.CountForQuizAsync had a missing closing ) on its dynamically-built NOT IN clause — the generated SQL was WHERE questionGuid NOT IN (@ex0, @ex1 with no closing paren, which SQLite correctly reports as SQLite Error 1: incomplete input. Affected all platforms when the quiz config page loaded with any non-empty exclude set. Fixed in both Gen2 and Gen3.
Feature — SHA-256 manifest verification
ExamulatorCore · ExamulatorUpdater · Examulator · Examulator3
The version manifest now supports an optional third whitespace-separated field per line: a 64-character hex SHA-256 digest of the download ZIP. Backward compatible — old clients see only the first two fields and ignore the third. When a hash is present, ExamulatorUpdater computes SHA256.HashDataAsync on the downloaded file after completing the download and aborts with exit code 4 on mismatch. Hash propagated through UpdateCheckResult.AppDownloadSha256 and the --sha256 CLI argument. AllDatabaseHashes dictionary added to UpdateCheckResult for future DB-download verification.
Fix — Android safe-area inset (status bar overlap)
ExamulatorMaui · Examulator3Maui
.top-bar had a fixed height: 52px while padding-top: env(safe-area-inset-top) consumed that budget on Android 15+ edge-to-edge devices (Pixel 10 Pro Fold and others), leaving the brand/nav buttons rendered behind the system clock. Fixed by changing to height: calc(52px + env(safe-area-inset-top, 0px)) so the bar expands rather than compresses. Both MAUI surfaces updated.
Fix — Android 300 ms tap delay
ExamulatorMaui · Examulator3Maui
touch-action: manipulation was scoped to .btn only, leaving all other interactive elements subject to the double-tap delay (manifesting as "must click multiple times" on foldable devices). Broadened to a, button, input, select, textarea, label, [role="button"], [role="tab"], [role="menuitem"] on both MAUI surfaces.
Feature — iOS platform support
ExamulatorMaui · Examulator3Maui
net10.0-ios added to TargetFrameworks in both MAUI csproj files. Platforms/iOS/ boilerplate added to both Gen2 and Gen3: AppDelegate.cs, Program.cs, Info.plist (minimal — MAUI merges bundle ID, version, and icon from csproj). publish-ipa.ps1 added alongside the existing publish-aab.ps1. <PackageVersion>1.0.0</PackageVersion> pinned in both MAUI csproj files to resolve NU1105 (iOS SDK sets PackageVersion from ApplicationDisplayVersion during NuGet restore; Android leaves it at the default, causing a mismatch).
Fix — Android adaptive icon
ExamulatorMaui · Examulator3Maui
android:roundIcon="@mipmap/appicon_round" removed from both Gen2 and Gen3 AndroidManifest.xml. MAUI does not generate appicon_round mipmap resources; launchers that specifically requested the round icon variant resolved to a missing resource and fell back to the generic Android system icon on some devices (notably Pixel launchers on Android 7.1).
Infrastructure — Version bumped to 26258.02-rc1 (12 sites)
Examulator · ExamulatorMaui · Examulator3 · Examulator3Maui · ExamulatorDiag · Examulator3Diag · ExamulatorTrace · Examulator3Trace
Daily revision bump .01 → .02 on Julian day 26258 (2026-09-15) for the PC theme-selection fix above. Channel unchanged (rc1). MAUI <ApplicationVersion> = 2625802 (26258 × 100 + 2).
[26247.01-rc1] — 2026-09-04
Scope: Both generations, all four surfaces.
Infrastructure and patch release. Julian date bump, Android icon fix, and iOS platform scaffolding.
Infrastructure — Version bumped to 26247.01-rc1 (12 sites)
Examulator · ExamulatorMaui · Examulator3 · Examulator3Maui · ExamulatorDiag · Examulator3Diag · ExamulatorTrace · Examulator3Trace
Julian date bump from 26232 to 26247 (2026-09-04). Channel unchanged (rc1). MAUI <ApplicationVersion> = 2624701 (26247 × 100 + 1). Android icon fix and iOS platform support included (see 26258.01-rc1 for full detail).
[26232.01-rc1] — 2026-08-20
Scope: Both generations, all four surfaces.
Adds a new Raw Question Review report (Tier 4 — Study Tools) and clears the compile-time breakage that surfaced after building 26228.01 against a fresh checkout. All changes mirrored across both product generations and all four surfaces (PC Gen2, PC Gen3, MAUI Gen2, MAUI Gen3).
Feature — Raw Question Review (Tier 4 — Study Tools)
ExamulatorCore · Examulator3Core · Examulator · Examulator3 · ExamulatorMaui · Examulator3Maui
A study-focused report — the first entry in a new Tier 4 "Study Tools" catalog band — that walks the user through questions across multiple sessions in the same detailed layout as the exam-result question review. Not an analysis: no charts, no scores, no aggregates. Purpose is re-drilling weak spots after several practice sessions have accumulated.
Configure screen additions. After picking an exam and sessions, an Include Questions section with four checkboxes: All questions (a check-all shortcut), Answered correctly, Answered incorrectly, Flagged for review. Filter semantics are a union — a question is included if it matches any selected category, so Answered correctly + Flagged for review surfaces every correct question plus every marked-for-review question that was answered incorrectly. Generate is gated on both ≥1 session and ≥1 filter category. The All checkbox and its three sub-boxes stay bidirectionally in sync (checking all three flips All on; toggling All flips the three).
Output screen. Sessions in chronological order, each with a heading (session index, date, mode, score/pass, matched-question count) followed by the matched questions in the order the user encountered them. Each question card mirrors the HistoricalResults render: status badge (correct/incorrect/unscored), review flag, domain, question text, answers with ✓/✗ markers and correct/wrong highlighting, and — on Gen3 — per-answer explanations, correct-answer explanation, and Further reading key reference.
Data path. New ResultsRepository.GetDetailsAsync(sessionGuids, username) batch loader loops the existing GetDetailAsync per session and returns the fully-enriched HistoricalSession list (question text, answers, explanations, marked-for-review), which ReportBuilder.BuildRawQuestionReview filters via a new RawQuestionFilter [Flags] enum (None / Correct / Incorrect / MarkedForReview / All).
PDF export. Uses the standard ReportPdfExportService path; renders each question in a two-column table (colored status stripe + content cell) so the block stays visually grouped across page breaks. Long reports (hundreds of questions) page naturally under MigraDoc. The BuildAsync signature gained an optional RawQuestionFilter rawFilter = RawQuestionFilter.All parameter — appended after Gen3's existing agency argument so no existing call site breaks.
Gen2 ↔ Gen3 renderer. Rather than fork the render code per generation, the Output pages (PC + MAUI) and the PDF service use small reflection helpers to resolve HistoricalQuestion.KeyReference, .CorrectExplanation, and HistoricalAnswerChoice.Explanation — properties present on Gen3, absent on Gen2. Absent properties resolve to empty strings, so the same markup renders on both.
Answer-required invariant. The exam UI (both PC and MAUI, both Linear/CAT and Quiz) does not allow advancing without a selected answer, and neither timer expiry (ExamSessionService.ApplyRootRule — RunOutOfTime) nor abort force-save the pending question, so IsAnswered is invariant on persisted rows. Marked-for-review therefore always implies answered. The filter still guards IsAnswered defensively.
DI. ResultsRepository was already registered as Scoped in every composition root (Examulator/Program.cs, Examulator3/Program.cs, both MauiProgram.cs), so the new ReportPdfExportService constructor dependency and the PC Reports/OutputModel dependency wire up automatically with no Program.cs changes.
Persistence between Configure and Output. PC threads rawFilter through TempData["ReportRawFilter"] alongside ReportKind / ReportExamCode / ReportSessions / ReportAgency; MAUI threads it through the URL query as &rawFilter=N. Both surfaces refresh cleanly.
Styling. Four new classes (.raw-filter-group, .raw-filter-item, .raw-session-heading, .raw-session-meta, .raw-empty-note, .raw-filter-summary) added byte-identically to site.css (both PC gens) and app.css (both MAUI gens), following the existing gold-accent visual language.
Catalog. Tier switches in Catalog.cshtml / Catalog.razor now map 4 => "Study Tools".
Fix — Razor tag-helper attribute conflict on <body> (_Layout.cshtml)
Examulator · Examulator3
.NET 10 treats <body> as a tag-helper element and rejects inline @(...) expressions in its attribute declaration area (RZ1031). The accessibility layer was emitting seven conditional data-a11y-* attributes with per-attribute expressions of the form @(!string.IsNullOrEmpty(x) ? $"data-a11y-x=\"{x}\"" : ""), which failed to compile.
Initial workaround concatenated the attributes in the top-level @{ } block and emitted them via <body@Html.Raw(bodyAttrs)>, which cleared RZ1031 but left the tag-helper parser unable to recognize the <body> start tag, so the matching </body> at file end triggered RZ1034 ("malformed body tag helper").
Correct fix: idiomatic ASP.NET tag-helper null-attribute pattern — give each attribute a proper name="@(cond ? value : null)" form. When the expression evaluates to null, the tag helper omits the attribute entirely (no empty data-a11y-theme="" rendered), no C# sits in the attribute-declaration area, and the tag remains well-formed. Also silences the four spurious CS0219 "assigned but never used" warnings on the a11yComfort / Motion / Focus / Timer locals — they were used, but the failed body-tag parse had hidden that from the analyzer.
Documentation — @ReadMe.md fully rewritten
@ReadMe.md
The distribution README had drifted badly since v26195.02-alpha (May 2026) — most of the document described a solution structure and roadmap that had been superseded by four to six months of shipped work. Rewritten from scratch to reflect the current product.
User-facing sections rewritten: - Supported Certifications — all nine ISC² certifications marked as Available (previously CC / CCSP / CGRC / CISSP / CSSLP available with SSCP / ISSAP / ISSEP / ISSMP as coming soon). Added a Native delivery mode column (CAT vs Linear per Pearson VUE) alongside Examulator's supported modes for each certification. New ISACA (via Examulator3 — in development) subsection. - Multi-platform framing — front matter now leads with "desktop/web AND Android"; previously implied desktop-only. Android installation subsection added with both Play Store package IDs (com.certificationtoolanddie.examulator for Gen2, com.certificationtoolanddie.examulator3 for Gen3). - Features — Reports subsystem added with all four tiers listed (Core / Advanced / Deep Dive / Study Tools including Raw Question Review); Accessibility program summarized (WCAG 2.1 AA target, ten settings, screen-reader chart tables, extended time, flag notes); Contribute Results named correctly (previously "Anonymous data sharing") and cross-referenced to Privacy-Policy.md §5.3; Diagnostic export mentioned. New Gen3 additions subsection covers per-answer explanations, key references, and the multi-agency schema. - Configuration table — verified against current appsettings.json; kept.
Developer-facing sections rewritten: - Solution Structure — was 6 projects (Examulator / ExamulatorCore / ExamulatorIrt / ExamulatorDiag / ExamulatorTrace / ExamulatorApi / ExamulatorTests), now correctly shows the current 18 projects grouped by Shared / Gen2 / Gen3. Root-namespace convention explained. - Tech Stack — added .NET MAUI Blazor Hybrid, MigraDoc/PdfSharp with the bundled Roboto font, Chart.js (both reportCharts.js and reportChartsMaui.js), and updated framework version. - Build Requirements — added MAUI workloads (dotnet workload install maui-android) and the AndroidTargetSdkVersion 36 Play Store deadline (Aug 31 2026). - Local Development — new note that Directory.Packages.props is the source of truth for NuGet versions and that per-project PackageReference versions must not be edited. - Data Model — new short section describing the exam DB vs Results.db split and the per-generation Results.db isolation. - Release Process — new short section explaining the YYDDD.VV-channel version format.
Sections removed: - Phase 2 Migration Notes (for developers merging v26195.02-alpha) — historical migration guidance for a release cycle four+ months in the past; no longer relevant. - MAUI Android Port (in progress) — MAUI ships as both Gen2 and Gen3 apps; the "in progress" framing was long stale. - Contact section expanded from a single mike@ address to all five published addresses (feedback@, contribute@, accessibility@, privacy@, mike@).
Infrastructure — Version bumped to 26232.01-rc1 (12 sites)
Examulator · ExamulatorMaui · Examulator3 · Examulator3Maui · ExamulatorDiag · Examulator3Diag · ExamulatorTrace · Examulator3Trace
Suffix changed from -closed to -rc1 for Google Play Early Access (open testing) submission. Standard 12-site bump. MAUI <ApplicationVersion> = 2623201 (26232 × 100 + 1). No display-format changes.
Infrastructure — Version bumped to 26231.01-closed (12 sites)
Examulator · ExamulatorMaui · Examulator3 · Examulator3Maui · ExamulatorDiag · Examulator3Diag · ExamulatorTrace · Examulator3Trace
Standard 12-site bump. MAUI <ApplicationVersion> = 2623101 (26231 × 100 + 1). No display-format changes.
[26228.01-closed] — 2026-08-16
Scope: Both generations, all four surfaces.
Major milestone release, versioned for Play Store closed-beta submission. Headline addition is the opt-in Contribute Results feature that anonymously transmits IRT calibration data to help refine question item parameters. Also lands a comprehensive accessibility program targeting WCAG 2.1 AA (settings, screen-reader chart tables, extended-time accommodation, flag notes — all four surfaces), the Exam Reports subsystem (three tiers of multi-session analysis), per-answer explanations and key references on Gen3, per-session kebab actions on the Single Exam Report configure screen (all four surfaces), MAUI action-sheet kebab pattern, collapsible Help sections on the web, two-column splash layout, build-timestamp via AssemblyMetadataAttribute reflection, returnUrl propagation for back-navigation, CSS contrast improvements across the web app, and a substantial bug-fix pass.
Feature — Contribute Results (opt-in IRT calibration data)
Examulator · ExamulatorCore · ExamulatorMaui · Examulator3 · Examulator3Core · Examulator3Maui · ExamulatorApi
Optional, opt-in feature that lets users transmit an individual exam result to a central calibration API to help refine question item parameters (discrimination, difficulty, guessing). Disabled by default; requires the user to request an API URL from contribute@certificationtoolanddie.com and paste it into a Setup screen.
Two-page flow. /Upload/Setup accepts the pasted URL (which contains the user's UUID API key as a query parameter) and validates its shape. /Upload/Confirm shows exactly which fields will be transmitted, offers a "Contribute" button and a Return button, and includes an Automatically contribute all future results checkbox the user can toggle any time.
Anonymous payload. Transmits only IRT parameters, per-question response outcomes, ability estimate (theta), timing, and session-level score/pass/termination. Explicitly excludes username, question text, answer text, explanations, key references, and any data from other sessions. Uses the existing ExamulatorApi POST /exam-results contract with X-Api-Key header (the key is stripped from the URL and travels as a header so it stays out of web-server logs).
Duplicate protection. The client stores an uploadedAt timestamp per session in Results.db (new column added via safe idempotent ALTER TABLE), and the server's existing IngestionService uses INSERT OR IGNORE on sessionGuid for idempotence. The confirmation screen shows "✓ Previously contributed on {date}" when applicable.
Settings storage. New SettingsRepository persists the API URL and auto-upload flag in a Settings table in Results.db, auto-created on first access.
Entry point. A "📤 Contribute Results" button on the bottom action bar of every HistoricalResults page (both PC and MAUI, both gens) opens the Confirm page for the currently-viewed session.
Privacy policy updated to add Section 5.3 documenting the feature in full, and Section 10 explaining the API-key-only deletion path.
Feature — Accessibility program (WCAG 2.1 AA target)
Examulator · ExamulatorCore · ExamulatorMaui · Examulator3 · Examulator3Core · Examulator3Maui
Comprehensive accessibility push spanning screen-reader support (Phase 1), low-vision and motion accommodations (Phase 2), cognitive aids (Phase 3), and motor accommodations (Phase 4). All four surfaces. Companion public statement in Accessibility-Statement.md.
Settings & persistence. SettingsRepository extended with ten a11y.* keys (theme, fontSize, readingFont, readingComfort, reducedMotion, enhancedFocus, hideTimer, extendedTime, flagNotes, largeTapTargets), persisted in the Settings table of Results.db.
Accessibility Settings page — all four surfaces. PC /Settings/Accessibility (both gens) with Visual / Reading / Timing / Review Aids / Motor sections, plus a new MAUI Blazor equivalent at the same route (both gens) using native label-wrapped checkboxes for full keyboard and TalkBack support. ♿ icon in the top nav on all four surfaces. A lightweight /Accessibility public statement page ships on PC.
Attribute plumbing. PC _Layout.cshtml reads settings and emits data-a11y-* attributes on <body> plus a skip-to-content link. MAUI adds a window.examA11y JS bridge in index.html and a new A11yInterop service; MainLayout applies settings on first render and the settings page re-applies them instantly on save — no restart needed.
CSS foundation (all 4 stylesheets). .sr-only and .skip-link utilities, light and high-contrast themes, large/x-large font scaling, Arial/Verdana/Tahoma reading fonts, reading-comfort typography, reduced motion (opt-in plus prefers-reduced-motion respect), enhanced :focus-visible rings, timer hiding, and — MAUI only — data-a11y-tap="large" rules enforcing 44 px minimum touch targets (WCAG 2.5.5 AAA) on buttons, answer rows, chips, selects, and nav links.
Chart table fallbacks (Reports). Every Reports chart was previously opaque to screen readers. All 8 <canvas> charts on the Reports Output page (both PC gens, both MAUI gens) now carry role="img" + descriptive aria-label, and 7 of them get a sibling sr-only data table rendered from the same view-model data that feeds Chart.js (score progression, domain trends, radar, difficulty mix, difficulty trends, pacing trend, pacing scatter summary; the weak-domains chart is covered by its adjacent visible ranked list). The Domain × Difficulty heat map gained a caption, proper scope headers, row headers, and per-cell sr-only "X of Y correct" text.
Extended-time accommodation (score-invalidating, clearly labeled). a11y.extendedTime (1.5× / 2×) is now consumed at session creation for Linear and CAT modes on all four surfaces via ExamSessionService.ApplyExtendedTime, multiplying both per-question and whole-exam limits so pacing scales too. The factor persists to Results.db (Results.extendedTimeFactor, schema v5 on Gen2 / v6 on Gen3) and both Results and Historical Results display a prominent "Extended time (N×) — score not comparable" banner for affected sessions. Quiz mode is untimed by default and unaffected.
Flag notes (cognitive aid). With a11y.flagNotes enabled, every flagged and answered question on Results and Historical Results (all four surfaces) offers a 500-character note field ("Why did you flag this question?"). Notes persist to a new ResultQuestions.reviewNote column via SetReviewNoteAsync / GetReviewNotesAsync; PC saves through a SaveNote AJAX handler with antiforgery protection, MAUI writes through the repository directly. Saved notes remain visible read-only if the setting is later switched off.
Form label fix (MAUI Upload/Setup). The API URL input on Upload/Setup.razor (both MAUI gens) now uses a real <label for="apiUrlInput"> instead of a plain <div>, matching the PC surface.
Non-native clickable fixes. MAUI QuizConfig check-rows (Timing / Difficulty / Domain / Pool — 7 rows) converted from <div @onclick> to the <label> + sr-only native <input type="checkbox"> pattern, giving full keyboard and TalkBack semantics. MAUI SelectMode mode cards (Quiz / Linear / CAT) converted from <div @onclick> to <button type="button"> with a UA-reset CSS block. PC SelectExam install cards and PC Reports/Configure session rows gained role="button" + tabindex="0" + Enter/Space onkeydown handlers; install cards also got a descriptive aria-label.
Emoji aria-hidden wrapping. Leading decorative emoji on buttons and labels across Results, Historical Results, and Question pages wrapped in <span aria-hidden="true"> so screen readers read the button text only.
Screen-reader fixes. Removed aria-label="Option A" overrides that hid answer text from AT; labeled the last unlabeled kebab menus and the Exam History search input; converted the question-count slider title to a real <label>; timer milestone announcements (10 m / 5 m / 1 m / 30 s / expiry) via role="status" live region without second-by-second chatter; answer-reveal announcements ("Correct. The answer is option A."); <PageTitle> added to 14 MAUI pages × 2 gens so TalkBack announces navigation.
Feature — Per-answer explanations & key references (Gen3-only)
Examulator3 · Examulator3Maui · Examulator3Core
The Gen3 exam schema carries a rationale on every answer choice — a full explanation for the correct answer and a distractor rationale for each wrong one — plus a per-question key reference (a further-reading citation). This release completes the UI rendering:
Results and Historical Results (PC + MAUI): each choice shows its own explanation inline. A "Further reading" key-reference line follows. When a question only carries the legacy single explanation, the previous correct-answer block is shown instead.
Live question view — Quiz mode (PC + MAUI): on "View Answer", if the user selected a wrong answer the card shows both the correct-answer explanation and a red-bordered distractor rationale explaining why their specific choice was wrong. Back-navigated reviewed questions show the same pair. Other distractors are not shown live — only the one the user actually chose. All four rationales remain available in post-exam Results review.
Key reference appears on the explanation card on reveal/review.
Report/results PDF export already emitted these fields; on-screen and exported content now match.
Gen2 is unchanged — it uses a single question-level explanation model.
Feature — Diagnostic export schema v3 (Gen3-only)
Examulator3Diagnostics · Examulator3Trace
DiagDoc.KnownSchemaVersion bumped "2" → "3". New fields on QuestionDetail:
KeyReference — further-reading citation from Questions.keyReference.
AnswerExplanations — per-answer pedagogical text fetched from the Answers table, keyed by answer GUID. Null when the exam DB was unavailable at export time.
DiagDocBuilder, SessionSnapshot, and DiagnosticExportService updated to populate and map the new fields. Gen2 stays at schema "2".
Hardening — Diagnostic export schema guard
ExamulatorTrace · Examulator3Trace
Both Trace tools now check a loaded diagnostic file's schemaVersion before rendering it, refusing (with a clear message) any document produced by a newer schema. A blank/absent version is treated as legacy v1 and still accepted. KnownSchemaVersion must be bumped in lockstep between the Diagnostics library and the Trace reader whenever the DiagDoc shape gains fields.
Feature — Exam Reports (three tiers of multi-session analysis)
Examulator · ExamulatorCore · ExamulatorMaui · Examulator3 · Examulator3Core · Examulator3Maui
A new Reports entry (📊, replacing the old Results nav link on MAUI) opens a report catalog; each report leads to a configuration screen where the user picks an exam and a set of sessions, then to a generated report with interactive charts.
Tier 1 — Single Exam Report: the classic single-session detail view, reached through the Reports catalog with the same interactions as Exam History.
Tier 2 — Domain Trends: per-domain scaled score over the selected sessions, with proficiency threshold overlay and colour-coded pass/fail bands.
Tier 3 — Comprehensive Analysis: aggregate summary combining domain trends, difficulty distribution, and time-per-question analysis across the selected sessions.
The ReportPdfExportService (both Core projects) produces a printable PDF matching the on-screen report. DomainTrendsViewModel.Series (was .Domains) fix applied when the per-domain aggregator was refactored.
Feature — Multi-session PDF export
ExamulatorCore · Examulator3Core
ReportPdfExportService renders reports covering many sessions to a single PDF using empira PdfSharp/MigraDoc. Roboto font is bundled so output is consistent across host platforms. Called from the Reports Output page on both PC and MAUI.
Feature — Single Exam Report row kebab (all four surfaces)
Examulator · ExamulatorMaui · Examulator3 · Examulator3Maui
On the Single Exam Report configure screen the multi-select checkbox list is replaced with clickable rows in the history-item visual style used by Exam History. Tapping a row navigates directly to that session's Historical Results view — no separate "Generate Report" step is needed for a single-selection report. A per-row ⋮ kebab exposes the same actions available from the Exam History kebab: View, Export PDF, Delete (with confirmation), and Export Diagnostic. Multi-session reports retain the checkbox + Generate Report pattern unchanged.
PC uses a shared floating context-menu positioned relative to the clicked kebab button, populated by a small vanilla-JS handler.
MAUI uses a bottom-sheet action panel rendered at the page root level with a semi-transparent backdrop. This escapes the per-row stacking context that would otherwise cause the menu to be clipped beneath subsequent rows.
The Last 5 / Last 10 / All quick-pick buttons and the "N selected" counter are hidden on the Single Exam Report screen since the report by definition requires exactly one session.
Feature — Return URL for HistoricalResults back button
Examulator · ExamulatorMaui · Examulator3 · Examulator3Maui
Both PC page models gained a [FromQuery] ReturnUrl parameter (default /ExamHistory) and both MAUI HistoricalResults gained [SupplyParameterFromQuery] ReturnUrl. All back buttons and post-delete navigation now respect the ReturnUrl query parameter. Callers from the Single Exam Report configure screen pass returnUrl=/Reports/Configure/0, so Back returns to the report selection screen rather than defaulting to Exam History. Callers from Exam History omit the parameter and get the previous behaviour.
Feature — Build timestamp via reflection
Examulator · ExamulatorMaui · Examulator3 · Examulator3Maui
Splash screens now show "Built {timestamp} UTC" beneath the version stamp. The timestamp is embedded in each entry-point assembly at compile time via an MSBuild AssemblyMetadata item, then read back at runtime via Assembly.GetCustomAttributes(typeof(AssemblyMetadataAttribute), false). Works on Android where Assembly.Location is empty because the attribute is embedded in the binary metadata, not resolved from disk. No appsettings.json maintenance required.
Feature — Collapsible Help sections (web)
Examulator · Examulator3
All sections on the Help page now render collapsed by default with only the numbered title row visible. Clicking any header expands or collapses that section. A ▼ chevron rotates 90° when collapsed. Implementation is CSS-only display toggling driven by a three-line querySelectorAll listener — no framework overhead.
Feature — Web splash — 2-column layout
Examulator · Examulator3
The splash screen now uses a two-column layout at viewports ≥820px: logo on the left, Important Notice card + footer stacked vertically on the right, vertically centred against the logo's height and horizontally centred within the right column. Mobile viewports fall back to the original single-column stack.
Feature — Reports catalog entry for Exam History
Examulator · ExamulatorMaui · Examulator3 · Examulator3Maui
"View Exam History" now renders as a full-width report-card entry in the Reports catalog, matching the width and visual style of the tier cards above it. On MAUI the 📋 Exam History icon has been removed from the splash screen and the top navigation bar — all history access flows through the Reports catalog.
Enhancement — MAUI Historical Results kebab
Examulator3Maui · ExamulatorMaui
HistoricalResults (the detail view for one session) gained its own ⋮ kebab in the header with Export PDF, Delete (with red confirmation bar), and Export diagnostic actions. Injections and delete-navigation identical to the Exam History kebab pattern.
Enhancement — MAUI Historical Results — difficulty by level collapsible
Examulator3Maui · ExamulatorMaui
The "By Difficulty Level — all domains" section moved from above the per-domain breakdown to below it, immediately before the Question Review divider. It is now collapsed by default with a ▼/▲ toggle in the same visual style as the Question Review toggle. Bottom bar "← Back to History" and "← History" labels simplified to "← Back".
Enhancement — Web quiz options readability & contrast
Examulator · Examulator3
Two CSS-variable-level contrast fixes:
--text-dim boosted from #3f5570 (~2.4:1 on card background) to #6080a2 (~4.6:1 — passes WCAG AA for small text).
--text-muted boosted from #7a95b0 to #8ab0cc (~6.5:1) for stronger small-label contrast.
Downstream effects (no markup changes needed): the "All" chip when no filter is applied, Time Remaining / Question # labels in the exam header, the "How this score was computed" title on results, metadata lines throughout the app, and the review objective strip all pick up the improved contrast automatically. .fc-label in Quiz Config (Difficulty, Domains, Question Pool labels) upgraded from --text-dim to --text-muted. The "10 (minimum)" and "N (full bank)" hint spans under the Question Count slider changed from text-dim to text-muted with the font size bumped from .78rem to .8rem.
Enhancement — Question triband icons removed (web)
Examulator · Examulator3
The clock icon next to "Time Remaining" and the question-mark icon next to "Question #" in the exam header removed; each stat block now goes directly label → value. The label row wrappers also removed since they only existed to hold the icons.
Fix — QuizConfig agency route regression (Gen3)
Examulator3 · Examulator3Maui
Gen3 QuizConfig was calling GetExamByCodeAsync(examCode) and GetDomainsAsync(examCode) with a single argument, but the Gen3 repository signatures require (agency, examCode). PC route changed from {code} to {agency}/{code}; MAUI route changed from /QuizConfig/{Code} to /QuizConfig/{Agency}/{Code}; both page models gained an Agency parameter and all repository calls updated. Missing using for ExamSession namespace added to both QuizConfig.cshtml.cs files.
Fix — MAUI Gen2 Configure agency drift (regression)
ExamulatorMaui
The Gen2 MAUI Configure page was accidentally carrying Gen3-specific AgencyCode references (exam.AgencyCode, dropdown value {agency}|{examCode}, filter comparing both fields). Reverted to the Gen2 signatures: dropdown value is exam.ExamCode alone, filter matches on ExamCode only, GetSessionsAsync called with the 3-arg Gen2 signature.
Fix — Razor @{ } regression in .NET 10
Examulator · Examulator3
The .NET 10 Razor compiler rejects @{ var x = ...; } code blocks placed between HTML elements inside an @if { } else { } body (RZ1010). Fixed by moving affected variable declarations (notes in Reports/Output.cshtml, reviewWrongExpl in Exam/Question.cshtml) to the top-level @{ } block at the head of each page.
Fix — Razor tag-helper attribute conflict on <body> (_Layout.cshtml)
Examulator · Examulator3
.NET 10 Razor treats <body> as a tag-helper element and rejects inline @(...) expressions in its attribute declaration area (RZ1031). The accessibility layer was emitting seven conditional data-a11y-* attributes with per-attribute @(!string.IsNullOrEmpty(x) ? $"data-a11y-x=\"{x}\"" : "") expressions, which failed to compile. Fixed by building the attribute string in the top-level @{ } block via StringBuilder and emitting it with @Html.Raw(bodyAttrs) on the <body> tag. This also silences the CS0219 "assigned but never used" warnings for the seven a11y* locals (they were used, but the failed body-attr parse hid that from the compiler).
Fix — DiagErrorBoundary ChildContent naming conflict
Examulator3Maui · ExamulatorMaui
In .NET 10 Blazor, using <ChildContent>@ChildContent</ChildContent> as an explicit slot inside <ErrorBoundary> when the outer component also declares [Parameter] public RenderFragment? ChildContent causes the source generator to emit a conflicting local variable (CS0841 / CS7014 cascade). Fixed with a private accessor _body => ChildContent referenced in the template. Also fixed literal newlines inside '\n' and "\n" char/string literals in the TrimStack method (CS1010 / RZ1000 / RZ1027 errors) with proper \n escape sequences.
Fix — ReportPdfExportService DomainTrends series name
ExamulatorCore · Examulator3Core
RenderDomainTrends iterated vm.Domains but DomainTrendsViewModel exposes per-domain data as Series (List<DomainTrendSeries>). Fixed in both Core projects.
Fix — SessionQuestion.Explanation → CorrectExplanation in Results review
Examulator3
Results.cshtml was referencing the non-existent q.Explanation in the legacy fallback block. Changed to q.CorrectExplanation (the computed property that returns the correct answer's explanation from the answers list).
Fix — File(inline: true) overload does not exist
Examulator3
PageModel.File() has no inline named parameter. Dropped to the two-argument overload File(pdf, "application/pdf") in HistoricalResults.OnPostExportPdfAsync — the browser displays inline by default when no Content-Disposition: attachment header is present.
Fix — Nullable analyser q.Answers in MAUI Question review
Examulator3Maui
Added a (q.Answers ?? []) fallback in the displayWrongExpl computation to satisfy the nullable analyser (CS8604), even though HistoricalQuestion.Answers is initialised to new().
Infrastructure — Version bumped to 26228.01-closed (12 sites)
Examulator · ExamulatorMaui · ExamulatorDiag · ExamulatorTrace · Examulator3 · Examulator3Maui · Examulator3Diag · Examulator3Trace
12 sites updated in lockstep: 4 appsettings.json (Version + Channel), 2 MauiProgram.cs fallback line pairs, 2 Diag/Program.cs (Version + VersionNumber const strings), 2 Trace/TraceInfo.cs (Version const), 2 MAUI .csproj (ApplicationVersion code = 2622801, ApplicationDisplayVersion = 26228.01-closed). Gen3 metadata (appsettings + csproj) advanced from placeholder 0.0.1-dev to full alignment with Gen2's release number for the first time.
Documentation — Privacy Policy expanded for Contribute Results
Section 5 gained a comprehensive new subsection describing the Contribute Results feature in full — how it is enabled, the exact fields transmitted, what is explicitly excluded, how the data is used, and how to disable or delete contributed data. Section 10 gained API-key-based deletion instructions. Sections 8, 9, 11, and 13 updated to reference the feature where relevant. Privacy-Policy.html now included at the root of the source zip alongside @ReadMe.md and both CHANGELOG-*.html files.
[26226.01-closed] — 2026-08-14
Scope: Both generations, all four surfaces.
Feature-heavy release: diagnostic-export workflow with a new shared library, kebab menu on the Exam History screen, print-preview page, and a click-the-exam-name Abort Exam mechanism. Also includes browser back-button protection on PC and a fix for a beforeunload false-positive that only surfaced in timed-quiz mode.
Feature — Diagnostic export workflow
The standalone ExamulatorDiag tool's DiagDoc-generation logic is now extracted into a shared library that both the standalone tool and the running application consume. Users can export a diagnostic JSON directly from the Exam History screen without needing to run the desktop tool.
ExamulatorDiagnostics (new class library, both generations)
Two new class-library projects: ExamulatorDiagnostics (namespace Examulator.Diagnostics) and Examulator3Diagnostics (namespace Examulator3.Diagnostics). Each references only ExamulatorIrt.
Contains the DiagDoc schema previously duplicated inside the standalone Diag tools: SessionSummary, QuestionDetail, DomainSummary, DiagnosticSummary, IrtTraceStep, plus input types (SessionSnapshot, QuestionSnapshot) that any producer populates and hands to the shared factory.
DiagDocBuilder.Build(session, questions, trace, version, supportEmail) is the single entry point. The ExamulatorVersion field is now runtime-passed rather than a hardcoded const — standalone Diag passes its build-time constant, the app passes the currently running App:Version + App:Channel combination, so every export self-identifies with the exact producing build.
IrtTraceBuilder.Build is a straight port of Diag's IrtEngine.BuildTrace. Algorithm and math delegated to ExamulatorIrt unchanged, so live-engine and diagnostic-tool output remain bit-for-bit reproducible.
Added SchemaVersion field to DiagDoc for future evolution — consumers should reject documents whose SchemaVersion is greater than what they understand.
Both libraries nested under their respective solution folders (Generation 2 / Generation 3) in Examulator.sln.
ExamulatorDiag standalone tool refactored
ExamulatorDiag/Models.cs and ExamulatorDiag/IrtEngine.cs removed; their content now lives in the shared library.
Database.cs rewritten to return library snapshot types directly instead of Diag-private row types.
Program.cs rewritten to call DiagDocBuilder.Build(...) and IrtTraceBuilder.Build(...) from the shared library. The standalone tool is now a thin CLI wrapper — new diagnostic features should land in the library, not here.
Same refactor applied to Examulator3Diag.
DiagnosticExportService (new, in ExamulatorCore + Examulator3Core)
App-side entry point for the "Export diagnostic" kebab action. Loads session and question rows via targeted SQL (bypasses HistoricalQuestion, which doesn't carry the IRT-relevant fields), populates snapshots, invokes the shared library, and returns (byte[] json, string filename) ready for download (PC) or share (MAUI).
Filename convention matches the standalone Diag tool: {ExamCode}_{Mode}_{Date}_{PASS|FAIL|UNKNOWN}_DIAG.json.
Gen3 version injects ExamRepository to resolve the exam's agency (Gen3 CreateExamConnection requires the (agency, examCode) pair, and Results.db doesn't carry agency). Falls back to "ISC2" if the master catalog is unavailable.
Registered as Scoped in all four startup projects.
Feature — Exam History kebab menu
Each row on the Exam History screen gets a ⋮ button on the right, opening a four-item floating menu:
👁 View — same as tapping the row (opens Results detail).
🖨 Print — navigates to the new print-preview page.
🗑 Delete — invokes the existing confirmation flow.
📤 Export diagnostic — builds the DiagDoc JSON and delivers it: PC downloads via File(bytes, "application/json", filename); MAUI hands to the Android Share sheet via Share.Default.RequestAsync with a ShareFile attachment. Users can email, save to Files/Drive, or attach to a message from the share sheet.
Row-tap behavior unchanged — still opens Results detail directly. Consistent affordance across PC and MAUI on both generations.
Feature — Print preview page
New page at /Exam/PrintPreview?sessionGuid=... for all four surfaces:
Paper-optimised layout: Georgia serif, muted borders, no navigation chrome, page-break avoidance on questions and headings.
"Include question review" toggle above the printable content — user can uncheck to get just the score + domain breakdown, avoiding a 30-page printout when only the summary is wanted.
Auto-fires window.print() after a 400 ms layout stabilisation delay.
On Android, window.print() hands off to the system print framework, giving real Save-as-PDF, connected printers, and share targets without any platform-specific PrintManager code.
@media print rules hide the toolbar during actual printing.
PC uses Layout = null with a standalone HTML doctype so site chrome doesn't leak into the printed output.
MAUI uses [SupplyParameterFromQuery] for route-parameter binding — Blazor idiomatic and needs no Microsoft.AspNetCore.WebUtilities reference.
Feature — Abort Exam
Discoverable way to abandon an in-progress exam without saving results, on all four surfaces.
Clicking (PC) or tapping (Android) the exam name in the upper-left of the question screen opens the abort dialog. Subtle hover/press opacity and a focus-visible outline confirm interactivity — no visible chrome added at rest.
Timer pauses while the dialog is open. On Continue Exam, ExamSession.StartedAt shifts forward by the exact pause duration so Elapsed stays continuous — works whether the dialog is open for 5 seconds or 10 minutes.
On PC, the client posts the pause duration to a new OnPostResume handler that compensates the server-side session.
Confirming discards the in-memory session and clears HttpContext.Session (PC) or deletes the checkpoint file (MAUI). Nothing is written to Results.db — the session is never SaveAsync-ed, so no partial rows leak.
Dialog copy: "Abort Exam? The results will be discarded." with a muted subtitle noting the timer is paused. Focus lands on Continue Exam by default so Enter/Space triggers the safe action.
Question state (selected answer, review flags) preserved on cancel — the dialog is a UI overlay, not a page unmount.
Escape key dismisses the dialog on PC (same as Continue).
FAQ entry added on all four Help surfaces documenting the mechanism.
Feature — Diag --version prints numeric-only
The standalone ExamulatorDiag --version and Examulator3Diag --version flags now print just the numeric portion (e.g. 26226.01) without the channel suffix. Build scripts embedding the version in a ZIP filename get a clean value without needing to strip the suffix themselves. The full display version continues to be used in the banner and embedded in DiagDoc exports.
Fix — Browser back button / tab close on PC
Prior to this release, hitting the browser's Back button, closing the tab, or refreshing the page during an exam silently abandoned the session with no confirmation.
window.onbeforeunload on the Question page now triggers the browser's native Leave site? confirmation for browser back, tab close, or refresh.
Suppressed via an _allowUnload flag for intentional form posts (Next / Back / Reveal / Abort / Resume / TimeExpired) so normal answering doesn't prompt.
No data-integrity risk in either case — nothing is written to Results.db until session completion, so an abandoned session leaves no partial rows.
MAUI (Android) hardware back button was already handled correctly via the existing LocationChangingHandler.
Fix — beforeunload guard false-positive on Next button (PC, timed exams)
Reported by user: "User does quiz, unchecks the Untimed Quiz option. Gets to question 1, answers, clicks Next and gets a browser warning 'Leave site? Changes will not be saved'."
Root cause: HTMLFormElement.submit() called programmatically does NOT fire the form's submit event. Only user-initiated submissions (clicking a <button type="submit">, pressing Enter in a text input) fire that event. This is a well-documented HTML/DOM quirk.
The timed-exam Next button uses <button type="button" onclick="submitAnswer()"> because it needs JS validation before submitting. submitAnswer() calls document.getElementById('answerForm').submit() directly — bypassing the submit event entirely. The _allowUnload bypass flag was being set by a form.addEventListener('submit', ...) listener that never fired, so beforeunload correctly saw an "unarmed" navigation and prompted.
The untimed-non-quiz path used <button type="submit"> on the answer form which naturally fires the submit event, so that path worked fine — which is why the bug only surfaced when the Untimed Quiz option was disabled.
Fix:
submitAnswer() now calls armAllowedNavigation() explicitly before .submit().
Added a click-listener on every <button type="submit"> as belt-and-braces for the natural-submit case.
Kept the old form.addEventListener('submit', ...) listener as an additional safety net for Enter-key submits — costs nothing.
Infrastructure — Per-project publish scripts removed
Every code project had a publish.cmd / publish.sh pair used for early ad-hoc publish scripting. Superseded by the top-level publish-aab.ps1 workflow (for MAUI Play Store submissions) and standard dotnet publish invocations for the other projects, so the per-project scripts were adding clutter without adding value. All 16 have been deleted (8 Gen2 + 8 Gen3 projects).
Infrastructure — Gen3 project rename to Examulator3{Module} convention
All Gen3 projects moved from the legacy ExamulatorX3 suffix form to the consistent Examulator3X prefix form:
Old name New name ---------------— ------------------ ExamulatorCore3 Examulator3Core ExamulatorMaui3 Examulator3Maui ExamulatorDiag3 Examulator3Diag ExamulatorApi3 Examulator3Api ExamulatorTests3 Examulator3Tests ExamulatorTrace3 Examulator3Trace
Each rename touched:
Project directory + .csproj filename
AssemblyName in every csproj
RootNamespace (except Examulator3Core, which retains Examulator3 as its shared root — mirrors Gen2's convention where ExamulatorCore uses Examulator as its root)
Every namespace declaration and using directive in .cs and .razor files
XAML x:Class and clr-namespace attributes
Every <ProjectReference> path in every csproj
Examulator.sln project paths + solution-folder nesting under "Generation 3"
Comments and docstrings referencing the old names
Zero legacy references remaining anywhere in the Gen3 tree after the rename.
[26223.02-closed] — 2026-08-11
Scope: Both generations, all four surfaces.
Consolidation release. Beta-line bug fixes, closed-testing track preparation, and copy corrections.
Fix — Abandon Exam button unresponsive (Android)
ExamulatorMaui · Examulator3Maui
LocationChangingHandler was firing recursively on confirm-abort. Fixed by adding an _exitConfirmed flag that short-circuits the handler on user-initiated abort.
Feature — Splash-screen version stamp
Examulator · ExamulatorMaui · Examulator3 · Examulator3Maui
PC and MAUI splash screens render Version {version} read at runtime from App:Version + App:Channel. Gracefully absent if keys are missing.
Fix — Data-sharing disclaimer copy
Examulator · ExamulatorMaui · Examulator3 · Examulator3Maui
Help, About, and FAQ updated to lead with "does not share any information" and mark the future IRT-upload feature as "Under development — not currently active".
Fix — ISC² CAT overclaims removed
Examulator · ExamulatorMaui · Examulator3 · Examulator3Maui
Help copy now claims only the class of technique without asserting identity with ISC²'s operational system. Linear mode card no longer describes itself as "paper-based".
[26222.10-alpha] — 2026-08-10 (Gen3-only)
Scope: Examulator3 · Examulator3Maui · Examulator3Core (Gen3-only).
Examulator3 · Examulator3Core · Examulator3Maui
Foundational Gen3 agency schema, repository signature refactor, and download-hang fix ported from Gen2. New Agencies table; Exams.agencyGuid FK; AgencyRepository; agency-tab UI on SelectExam. File-naming conventions locked. Every per-exam method in Examulator3Core now takes (agency, examCode).
[26222.02-alpha] — 2026-08-10 (Gen3 fork established)
Scope: Gen3 fork established — Examulator3 · Examulator3Core · Examulator3Maui · Examulator3Diag · Examulator3Trace · Examulator3Api · Examulator3Tests · Examulator3Diagnostics.
Examulator3 · Examulator3Core · Examulator3Maui · Examulator3Diag · Examulator3Trace · Examulator3Api · Examulator3Tests
Gen3 forked from Gen2 checkpoint 26221.17-alpha. Namespaces renamed (Examulator.* → Examulator3.*); fresh ApplicationId and ApplicationTitle (Examulator3). Schema additions: Question.KeyReference, Answer.Explanation, AnswerChoice.Explanation, Exam.AgencyCode, Exam.AgencyGuid. Removed: Question.Explanation (replaced by per-answer explanations on the correct choice). DatabaseFactory takes (agency, examCode) on every per-exam method.
[26221.17-alpha] — 2026-08-09 (Gen2 checkpoint — immutable pre-Gen3 baseline)
Scope: Examulator · ExamulatorCore · ExamulatorMaui and supporting projects — immutable Gen2 checkpoint; Gen3 projects did not yet exist.
Examulator · ExamulatorCore · ExamulatorMaui
Snapshot taken immediately before the Gen3 fork. Preserved as the immutable reference baseline; always included in handoff zips. All entries below are Gen2 only.
[26220.06-alpha] — 2026-08-08
Scope: Examulator · ExamulatorCore · ExamulatorMaui (Gen2 only — pre-Gen3 fork).
Examulator · ExamulatorCore · ExamulatorMaui
Closed-testing push. Quiz History filter corrected for mixed Unanswered + Answered selections. Central Package Management adopted (Directory.Packages.props); CentralPackageTransitivePinningEnabled = false; CentralPackageFloatingVersionsEnabled = true.
[26216.02-alpha] — 2026-08-04
Scope: Examulator · ExamulatorCore · ExamulatorMaui (Gen2 only — pre-Gen3 fork).
Examulator · ExamulatorCore · ExamulatorMaui
Quiz Config difficulty filter chips — restrict the question pool to selected difficulty bands. Question history filters — Unanswered Only / Include Incorrect / Include Correct / Include Marked-for-Review; shared BuildHistoryFilterAsync service. MAUI question-count slider — replaces numeric input, steps by 5.
[26216.01-alpha] — 2026-08-04
Scope: Examulator · ExamulatorCore · ExamulatorMaui (Gen2 only — pre-Gen3 fork).
Examulator · ExamulatorCore · ExamulatorMaui
Exam History detail page — tapping a row opens paginated question-by-question review with answers and explanations. MAUI session-persistence checkpoint — written on every answer submit, restored on cold-start if the user was mid-exam.
[26182.01-alpha] — 2026-07-01
Scope: Examulator · ExamulatorCore · ExamulatorMaui (Gen2 only — pre-Gen3 fork).
Examulator · ExamulatorCore · ExamulatorMaui · ExamulatorIrt
IRT-powered CAT mode — full 3PL IRT: Newton–Raphson MLE theta, maximum-information item selection, three-rule termination (CI boundary, max questions, time), scaled score anchored at θ = 0 → 700. Linear exam mode — stratified random selection across domains weighted by published ISC² percentages, difficulty-balanced allocation per domain.
[26167.01-alpha] — 2026-06-16
Scope: Examulator · ExamulatorCore · ExamulatorMaui (Gen2 only — pre-Gen3 fork).
Examulator · ExamulatorMaui · ExamulatorCore
Quiz mode — untimed (or timed) with immediate answer reveal, Mark for Review, and Feedback flags. Back-navigation through answered questions. Database download and installation — Google Drive–hosted exam DB ZIP download, extraction, and placement on both PC and MAUI. Progress UI on both surfaces.
[26160.01-alpha] — 2026-06-09
Scope: Examulator · ExamulatorCore (Gen2 only — pre-Gen3 fork; MAUI port not yet in tree).
ExamulatorMaui
MAUI Blazor Hybrid app scaffolded — net10.0-android, shared Razor components, IDataPathProvider abstraction. autostart="false" on blazor.webview.js required to prevent Blazor starting before the MAUI native handler is ready.
[26155.01-alpha] — 2026-06-04 (Initial alpha release)
Scope: Examulator (Gen2 only — initial alpha; single-project solution).
Examulator · ExamulatorCore · ExamulatorIrt
PC web application — ASP.NET Core 10 Razor Pages. SelectExam, SelectMode, Question (CAT / Linear / Quiz), Results, Exam History. ExamulatorCore — SQLite via SourceGear.sqlite3, DatabaseFactory, repository layer. ExamulatorIrt — 3PL IRT math: raw-to-float conversion, P(θ), Newton–Raphson MLE, maximum-information selection. Shared by PC, MAUI, Diag, and unit tests.