* feat: password reset via symfonycasts/reset-password-bundle (#179)
Implements the full password reset flow using the SymfonyCasts reset-password-bundle.
* ci: share base layer cache between dev and prod builds
* fix: use CSS form selector in tests instead of translated button text
* fix: translate missing validator string for reset password email field
* fix: translate reset password form labels via TranslatorInterface
* ci: override MAILER_DSN to null for PHPUnit so mailer host is not required
* feat: quiz page question rework (#181)
- Replace Bootstrap accordion with flat card list for questions
- Add HTML5 drag-and-drop reordering with placeholder-between-cards UX
and amber/green/red save status indicator next to the heading
- Add edit button per question opening a Bootstrap modal (bo--modal-form
Stimulus controller with X-Modal-Request header pattern)
- Show read-only view button instead of edit for locked/finalized quizzes
- Add BankQuestion edit modal in question bank tab using same infrastructure
- Move modal action buttons into modal-footer via <template data-modal-footer>
- Fix IS_AUTHENTICATED_FULLY 403: replace ROLE_USER with IS_AUTHENTICATED
on all backoffice controllers and in security.yaml access_control
* feat: answer field UX improvements
- Auto-add one empty answer field when opening a blank question form
- Auto-append new empty field when typing in the last answer field
- Strip empty answer rows before submit (novalidate + JS cleanup)
- Tab key skips correct/delete buttons, jumping straight to next answer
* feat: add bank question via modal + dirty modal guard
- Add question in question bank now opens a modal instead of navigating
to a full-page form, consistent with the edit modal pattern
- Modal closes are blocked by static backdrop once the user has made any
change (input, checkbox, drag-reorder, sort, randomize, remove answer)
- Dirty state resets when the modal is fully hidden
* fix: modal save button + missing translations
- Replace form="id" cross-element approach with requestSubmit() for
reliable save button wiring in modal footer
- Re-call _bindDirty after validation-error re-render so dirty guard
is preserved across save attempts
- Translate missing Dutch strings: Order saved, Error saving order,
Question details, View
* feat: replace fetch-based modal forms with Turbo Frames (#181)
Enable @hotwired/turbo with Drive explicitly disabled, then migrate the
bo--modal-form Stimulus controller (custom fetch + X-Modal-Request pattern)
to a thin bo--modal controller that lets Turbo handle HTTP and DOM swap.
Adds frame templates for quiz questions and bank questions; controllers now
detect Turbo-Frame header instead of X-Modal-Request.
* fix: exclude auto-generated reference.php from pre-commit CS-fixer
* fix: use correct Turbo v8 session export to disable Drive
* fix: address CodeRabbit review findings on PR #183
- Add SeasonVoter::EDIT guard to QuizQuestionController::view()
- Add full-count check in reorder() to reject partial ordering payloads
- Retry once in _persistOrder(); lock drag and show dismissible alert on failure
- Remove tabindex="-1" from correct-answer toggle button (accessibility)
- Replace 'EMPTY'|trans placeholder with proper copy
- Add data-modal-title to question bank Edit button to prevent stale title
* fix: correct Dutch translations flagged in review
- 'Error saving order': 'Fout bij het opslaan van de volgorde' (consistent with sibling strings)
- 'Owner(s)': 'Eigenaar/Eigenaren' (unambiguous, correct Dutch plural)
* fix: set explicit form action URLs for Turbo Frame modal forms
When a form has no explicit action, Symfony renders action="" which the
browser resolves to the page URL, not the URL the Turbo Frame was
fetched from. This caused edits submitted via the modal to POST to the
wrong route and silently discard changes.
* fix: preserve answer ordering on save and add coverage for all sort operations
- Enable Turbo Drive and use visit() in modal controller so submit redirects to the page under the modal
- Add removeAnswer() to Question entity so Symfony form can manage the collection with PHP 8.5 private(set)
- Remove applyAnswerOrdering() from QuizQuestionController and QuestionBankController — it iterated the Doctrine collection in its old DB order and overwrote the ordering values submitted from the form
- Add QuizQuestionControllerTest covering answer ordering preservation and question reordering within a quiz
- Extend QuestionBankControllerTest with answer ordering tests for both new and edit bank questions
* fix: translate three missing Dutch strings in nl.xliff
* Updated CLAUDE.md
* refactor: replace addEventListener with Stimulus data-action in modal and question-list controllers
* refactor: replace addEventListener with Stimulus data-action in form-collection controller
Move drag-and-drop and auto-expand event handling from imperative addEventListener
calls to declarative data-action descriptors in answer_row.html.twig and the
collection target templates. Stimulus MutationObserver picks up the descriptors on
dynamically added rows, removing the need for _makeDraggable(). The ancestor-form
submit listener stays as addEventListener since Stimulus data-action cannot reach
elements outside the controller's subtree.
* feat: show label colour badges in question bank edit form
Replace plain checkbox text with coloured Bootstrap badge pills in the
labels section of the bank question edit form (both standalone and modal
frame variants). Adds choice_attr to pass data-colour to each checkbox,
then renders the input manually so the label can hold the badge without
the Bootstrap 5 form theme wrapping in a second plain-text label.
* feat: add question bank management, quiz finalization, and related backend/frontend functionality
* chore: add symfony/object-mapper dependency and fix Finalized translation
* feat: address PR review comments — unassign/sync bank questions, blank quiz creation, deactivate redirect, remove duplicate tab titles
- Use Symfony ObjectMapper for BankQuestion/BankAnswer → Question/Answer copy (#[Map(if: false)] on id, season, etc.)
- Track created Question on BankQuestionUsage (nullable FK, onDelete: SET NULL) for unassign/sync support
- Add unassign route: removes the Question copy + usage record
- Add sync route: pushes bank question edits to a finalized-not-started quiz copy
- Auto-sync non-finalized quiz copies on bank question edit; flash warning for finalized-not-started
- Add blank quiz creation (no XLSX required) with new route + template
- Deactivate quiz button now stays on the quiz overview page (redirect_quiz hidden field)
- Remove duplicate h4 titles below the tab bar on all season tabs
- Add migration for bank_question_usage.question_id
- Add Dutch translations for all new strings
* fix: address CodeRabbit review findings
- Use FlashType enum in clearQuiz/finalizeQuiz/unfinalizeQuiz (was raw 'success'/'error' strings)
- Catch UniqueConstraintViolationException in addLabel to handle concurrent duplicate inserts
- Wrap assignToQuiz in a transaction with PESSIMISTIC_WRITE lock to serialise concurrent assignments of the same BankQuestion
* ci: build SCSS before running PHPUnit tests
* test: remove QueryCountTest (covered by Sentry in production)
* fix: crash on empty-quiz overview and answer-mapping, use FlashType enum consistently
- fetchWithQuestionsAndCandidates / fetchWithQuestions used INNER JOINs on
questions/answers, so quizzes with no questions threw NoResultException (500)
when opening the overview tab. Switched to LEFT JOINs.
- answerMapping bare \assert() replaced with a proper flash + redirect when
the quiz has no questions, instead of crashing with AssertionError.
- Three raw 'success' flash strings in QuizController replaced with FlashType::Success.
- Added Dutch translation for "This quiz has no questions yet".
- Two new tests: empty-quiz overview loads (200), answer-mapping redirects with flash.
* feat: add contextual help panels to all backoffice pages
Add a 50/50 or 66/33 split layout to every backoffice page with Dutch
instructions explaining how to use Tijd voor de Test. Content covers the
overall workflow, quiz finalize/activate flow, and both candidate
participation methods (own device vs. shared laptop).
Help text lives in dedicated partials under templates/backoffice/help/ and
is loaded via Twig include(), keeping page templates clean. All strings use
a separate 'instructions' translation domain (instructions+intl-icu.nl.xliff)
isolated from the main messages domain.
Also updates 'Finalize'-related Dutch translations to use 'Afronden' and
adds tooltips to the finalize/unfinalize buttons.
* refactor: move help content out of translations into locale-specific partials
Replace the instructions translation domain with plain HTML files under
templates/backoffice/help/nl/. Each help/*.html.twig is now a locale
dispatch shim that tries the current locale first and falls back to nl,
so adding English (or any other language) is simply a matter of creating
a help/en/ directory with the translated files — no code changes needed.
Removes instructions+intl-icu.nl.xliff.
* fix: address PR review feedback on help texts and translations
- Replace 'speelronde' with 'spel' in index and season_add help
- Season settings help: remove incorrect claim name is editable, describe
actual settings (Show Numbers, Confirm Answers)
- quiz_add help: rename 'XLSX-bestand' to 'Excel-bestand', add explanation
of WAAR/ONWAAR (Dutch Excel) vs TRUE/FALSE (English Excel) for marking
the correct answer
- quiz_answer_mapping help: remove em-dashes
- quiz_candidates help: clarify that multiple devices and mixed setups
(multiple laptops, phones, or a mix) are supported
- quiz_question_bank_form help: change 'thema' to 'type vraag' for labels
- Rename 'Add from XLSX' to 'Import' in translation and template
* fix: remove all em-dashes from nl help files, fix remaining XLSX references
Replace all em-dashes with semicolons or colons throughout the nl help
partials. Also replace remaining 'XLSX-bestand' with 'Excel-bestand' in
season_tests and index, and also also fix the em-dash that was still on
the same line as the 'speelronde -> spel' fix in index.
* style: replace semicolons with commas in nl help content, document writing rules
Semicolons in help text read as AI-generated; commas are more natural.
Added writing style rules to CLAUDE.md to prevent recurrence.
* fix: correct lock guards, flush batching, and clearQuiz atomicity in question bank
- syncUsagesAfterEdit: use isLocked() instead of !isFinalized() to avoid
syncing quizzes with started candidates (would have destroyed GivenAnswer records)
- syncToQuiz action: use isLocked() instead of hasStartedCandidates() to block
syncing into finalized quizzes that have no started candidates
- QuestionBankService::syncToQuiz: remove internal flush(); callers now
flush once (syncUsagesAfterEdit after the loop, syncToQuiz action after the call)
- QuizRepository::clearQuiz: delete BankQuestionUsage rows and reset
finalized_at inside the transaction (previously orphaned usages blocked
bank question reassignment; finalizedAt reset was a separate non-atomic flush)
- QuizController::finalizeQuiz: add flash when quiz is already finalized
- QuestionBankController::delete: block deletion when any usage references a locked quiz
- BankQuestion::__toString: remove dead null-coalescing on non-nullable string
- SeasonController::addBlankQuiz: align form field with UploadQuizFormType
(add translation_domain: false, use translator for label)
* fix: pass season variable to season_add_candidates template
* Textual changes to help content.
* Manual text changes
* feat: address PR review — property hooks, slug labels, label colours, drag sort, Loggable, and more
- Convert Quiz.isFinalized/isLocked/hasStartedCandidates and BankQuestion.isUsed/canBeAssigned to PHP 8.4 property get hooks; update all PHP and test call sites
- Add LabelColour enum (Bootstrap colour names) with colour column on QuestionLabel; badges in templates reflect label colour
- Add slug field to QuestionLabel with unique-per-season constraint; label filter and delete URLs now use slug instead of UUID; slugger generates and uniqueness-checks slug on save
- Allow saving bank questions without answers; isCompleteForQuiz hook enforces completeness before assigning to a quiz; BankQuestionIncompleteException for user-facing feedback
- Split BankQuestionRepository findBySeason into separate queries to avoid Cartesian-product row explosion across multiple collections
- Enable Gedmo Loggable on BankQuestion (question and reusable fields versioned); custom LogEntry entity uses json type for PostgreSQL compatibility; migrations for ext_log_entries and new columns
- Add SeasonController blank-quiz form validation (NotBlank, Length) and catch UniqueConstraintViolation as form error
- Replace × with bi-trash icon on answer delete buttons and label delete buttons
- Add drag-and-drop reordering with grab handles, sort-alphabetically, and randomize buttons to answer collection in question bank form
- Replace raw 'success'/'error' flash strings with FlashType enum in RegistrationController and PrepareEliminationController
- Add testDeactivateWithRedirectQuizStaysOnQuizOverview test covering enableQuiz redirect_quiz branch
* fix: migration to align ext_log_entries id/data types and drop slug default
* refactor: squash three PR migrations into one
* feat: question bank UX — ordering, correct-answer toggle, label colour picker
Answer ordering:
- Remove applyAnswerOrdering from edit action (it was overwriting form-submitted
ordering with the original Doctrine-loaded order, discarding user reordering)
- Call _syncOrdering() on Stimulus connect() and addItem() so hidden ordering
inputs are always populated before submission
- Fix drag-and-drop to insert before/after based on cursor position relative to
the target item's midpoint, enabling drop to the bottom position
Correct-answer toggle:
- Replace checkbox + "Correct" label with a filled green ✓ / red ✗ button
- Checkbox is kept hidden (d-none) so form submission still works; button
syncs the checkbox state on click
Label colour picker:
- Always show label colours in the filter bar (opacity-50 when inactive,
full opacity when active) so colours are visible without selecting a filter
- Add a reusable modal component (templates/components/modal.html.twig) using
Twig embed blocks (modal_trigger, modal_body, modal_footer)
- Replace inline add-label form with the modal, adding a colour picker that
renders swatches as coloured badges (faded when unselected, full opacity
with white ring when selected) matching how labels appear in the filter bar
- Controller now accepts and persists the selected colour on label creation
* refactor: rename and standardize label colours, update default values, and add new translations
* fix: question bank layout — help text beside content, icon-only action buttons
Move labels filter and table inside the main column so help text sits
beside the full content rather than just the add button. Convert Edit,
Delete, and Assign buttons to icon-only btn-group to save row space.
* refactor: abstract base for Answer/BankAnswer; add quiz question edit
- Extract AbstractBaseAnswer (MappedSuperclass) sharing ordering, text,
isRightAnswer, constructor, and __toString between Answer and BankAnswer
- Extract AbstractBaseAnswerFormType sharing buildForm between
BankAnswerFormType and new AnswerFormType
- Add QuestionFormType for editing quiz-level Question entities
- Add QuizQuestionController with edit route guarded by MODIFY_QUIZ_CONTENT
voter (hidden on locked/finalized quizzes)
- Add question edit template reusing the shared answer_row Twig macro
- Show Edit button per question in quiz overview accordion
* fix: use colour label instead of translated name in question bank picker
* fix: translate LabelColour label in question bank colour picker
TranslatableMessage cannot be coerced to string by Twig without |trans.
Adds the previously uncommitted quiz nav partial that broke main, and
introduces TemplateReferencesTest which scans all Twig templates for
extends/include/embed references and asserts each target file exists —
preventing this class of missing-template mistake from reaching CI undetected.
* Add CLAUDE.md, replace Makefile with Justfile, remove .junie
- Add CLAUDE.md with project overview, commands, architecture, and domain entity docs
- Remove Makefile in favour of the existing Justfile
- Remove .junie/AGENTS.md (knowledge transferred to CLAUDE.md)
- Update .gitignore: drop .junie/ entries, add .claude/settings.local.json
- Minor doc fixes in config/reference.php (typo, type correction)
* Clean up templates and CSS
- season.html.twig: remove dead empty column, drop redundant flex-row
- tab_overview.html.twig: extract Twig macro for confirm modals, fix duplicate aria-labelledby IDs
- tab_result.html.twig: remove dead comment, replace inline widths with CSS classes, simplify nested row/col forms to d-flex gap-1
- backoffice.scss: add col-result-xs/sm/md column width classes
- quiz.scss: replace broken display:grid + justify-self:center with flexbox centering
* Implement quizToXlsx() export and add export button
- QuizSpreadsheetService: implement quizToXlsx() as the inverse of
fillQuizFromArray() — writes quiz questions and answers to XLSX using
the same column layout as the import template
- BackofficeController: add exportQuiz() action at GET /backoffice/quiz/{quiz}/export
- tab_overview.html.twig: add Export to XLSX button in Quick actions
* Add unit tests for QuizSpreadsheetService
7 tests covering generateTemplate(), quizToXlsx(), and xlsxToQuiz():
- valid XLSX output and MIME type
- template without example reimports as empty
- template example data survives a reimport
- round-trip (export → reimport) preserves questions, answers, and correct flags
- empty quiz exports and reimports cleanly
- invalid MIME type throws InvalidArgumentException
- question with no answers throws SpreadsheetDataException with error list
* Fix quiz page vertical centering regression
The CSS cleanup broke vertical centering: flex on body causes main to
stretch full-width; place-items:center on a grid body only centers
items within their auto-sized track (not the track within body).
Fix: move background/color to html (full-viewport grid that centers
body), give body height:100% + display:grid + align-content:center
(centers the content track within full-height body) + justify-self:center
(shrink-wraps body width). Matches production behavior exactly.
* Improve quiz page layouts: WIDM-style answers and responsive centering
- Add green square answer buttons styled after the TV show
- Two-column answer grid for 6+ answers, single column on mobile
- fit-content centering for question pages so block matches question width
- Narrow fixed-width centering for form pages (enter name, select season)
* Use HeaderUtils::makeDisposition() for safe Content-Disposition filename
* Fix quizToXlsx to support unlimited answers and add header count tests
- Replace hardcoded 6-column arrays with dynamic Coordinate arithmetic
- Write data rows first to determine max answer count, write headers last
- Replace try/catch ErrorException in fillQuizFromArray with array_key_exists
- Add data-provider test covering 2, 6, 7, and 10 answers
- Add cross-question max-header and 7-answer round-trip tests
* Fix Sass healthcheck
* Improve quiz layout: add fixed topbar, include navigation, and clean up unused elements
- Add `.quiz-topbar` with fixed positioning and spacing in `quiz.scss`
- Update `base.html.twig` to include `quiz/nav.html.twig` in a new `nav` block
- Remove unused "Manage Quiz" button from `select_season.html.twig`
* Refactor generateTemplate to reuse quizToXlsx and add second example question
- generateTemplate now builds an in-memory Quiz entity and delegates to
quizToXlsx, eliminating duplicate spreadsheet-building logic
- Adds a second example question "Wie is de mol?" with 10 Dutch names
(5 male, 5 female) to better illustrate the import format
- Updates tests to assert both example questions and adds a test for the
blank-row halt behaviour in fillQuizFromArray (achieving 100% coverage)
* Move PHPUnit cache to /tmp to avoid writing into the mounted volume
* Update src/Service/QuizSpreadsheetService.php
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* Update composer.lock with dependency upgrades and improvements
Updated multiple dependencies in `composer.lock` to their latest versions, including upgrades for Doctrine, PHPUnit, Symfony components, and extended PostgreSQL support.
* Fix sass compile
CI / Build and deploy to ${{ startsWith(github.ref, 'refs/tags/') && 'production' || (github.ref == 'refs/heads/main' && 'acceptance' || '') }} (push) Has been skipped
* Some tests
* More tests!
* Tests 3
* Move getScores from Candidate to Quiz
* Add some suggestions for future refactoring
* - **Add Gedmo doctrine-extensions and Stof bundle integration**
- Added `stof/doctrine-extensions-bundle` and `gedmo/doctrine-extensions` dependencies.
- Integrated `Timestampable` behavior for `Created` fields in entities.
- Updated `bundles.php` to register StofDoctrineExtensionsBundle.
- Added configuration for the Stof bundle.
- Simplified `SeasonVoter` with `match` expression and added new tests.
- Minor fixes and adjustments across various files.
* WIP
* All the tests
* Base64 tests
* Symfomny 7.4.0
* Update
* Update recipe
* PHP 8.5
* Rector changes
* More 8.5
* Things
- Replaced getters/setters with direct property access across entities and repositories.
- Added and configured `martin-georgiev/postgresql-for-doctrine` for PostgreSQL enhancements.
- Updated Doctrine configuration with types, mappings, and JSONB query functions.
- Removed unused `EliminationService` and related YAML configurations.
- Introduced `tests/symfony-container.php` for bootstrapping Symfony.
- Updated `rector.php` with new container PHP path and kernel modifications.
- Registered Symfony routes provider service in Rector.
- Adjusted migrations to align with refactored ORM annotations.
- Added new PHPStan and PHP-CS-Fixer configurations, including stricter rules.
- Introduced `tests/object-manager.php` to improve test environment setup.
This commit introduces the ability to clear quiz results and delete quizzes directly from the backoffice. It includes new routes, controllers, modals for user confirmation, and updates to translations. The `QuizRepository` now supports dedicated methods for clearing results and deleting quizzes along with error handling. Related database migrations and front-end adjustments are also included.
This commit introduces functionality to manage candidate corrections in the backoffice, with updated templates and a new route handler. The SeasonVoter is refactored to support additional entities, and scoring logic is updated to incorporate corrections consistently. Includes test coverage for voter logic and UI improvements for score tables.