All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Assistant.openwebui_configJSON column for storing the uploaded OpenWebUI export verbatim, plus a create form at/assistant/newwith a file-upload field that AJAX-validates the JSON before submit and writes the result into an editable textarea (users can also paste/type JSON directly). A sharedApp\Validator\OpenWebUiConfigValidatorservice hosts the validation pipeline (JSON syntax + a temporary slow-validation scaffold for UI testing); the uploaded file itself is never persisted, only the parsed JSON reaches the database. The create form picks up the project's default-deny gating: any authenticated user can submit, no admin role required (#14).- Higher-contrast zebra striping on the shared
<twig:Table>component. Even rows now use a new dedicated--color-row-alt(#f1f3f5) design token instead of the near-white--color-surface-2, so admin lists read as alternating light-gray / white rather than a wall of white. Odd rows are made explicitbg-bgso the stripes survive tinted backgrounds.--color-surface-2is left alone — it remains the project's hover-state tone (#130). app:user:updateconsole command that updates an existing user's display name, roles, and / or lifecycle status. Each field is optional — omitting it leaves the value untouched.--rolemay be repeated to set multiple roles and replaces the current role list wholesale; unknown role identifiers and unknown status strings are rejected with a clear message. Sits behind a newUserManager::updateUser()service method (#122).task site-installlearned an opt-inRESET=1parameter (RESET=1 task site-install) that drops and recreates the application database before running migrations, so a fresh install on top of an existing schema doesn't fail with "table already exists". The default invocation stays non-destructive.- Foundation for outbound transactional mail:
symfony/mailerinstalled and wired to the existingmailcontainer (Mailpit) viaMAILER_DSN=smtp://mail:1025in.env, with a deploy-overridableMAILER_FROMsender..env.testpinsnull://nullso the test suite never hits a real transport. Genericsettingtable (nameunique,valuenullable text) backs anApp\Settings\SettingsManagerservice exposing a typedgetAdminRecipient()/setAdminRecipient()pair. A new admin-only page at/admin/settings(ROLE_ADMIN, CSRF on POST, 422 on invalid email) lets administrators set the recipient for registration-related notifications; the user-menu surfaces it under Administration → Indstillinger when the acting user is a site admin. Mail templates and the actual signup/admin-notification /one-time-login flows land in follow-up work (#119). - Data fixtures now assign a creating user (round-robin
alice@example.test/bob@example.test) to each seeded assistant and organization, so the created-by/modified-by blame relation is exercised by local-development data. - Integrated
itk-dev/entity-bundleas the shared entity foundation. NewApp\Entity\AbstractEntityextends the bundle'sAbstractITKDevEntity, giving every domain entity a ULID primary key plus timestamps, created-by/modified-by blame, archivability, and anonymization status.User,Assistant, andOrganizationnow extend it (integer ids replaced by ULIDs), are marked#[Auditable], andUser's PII is annotated with#[Anonymize]. All bundle features are enabled except soft delete (config/packages/itk_dev_entity.yaml);damienharper/auditor-bundleis wired for the audit log. Admin route{id}requirements (Userapprove/block,Organizationedit/delete) acceptRequirement::ULIDinstead of\d+. Records the decision in ADR 007 (#104). - Admin CRUD for
Organizationat/admin/organization(list, create, edit, delete) per ADR 003. Built withsymfony/form(newly added dependency) + raw Twig templates, multi-value email-domain field via a textarea with aCallbackTransformer, and Danish UI copy underadmin.organization.*. Auth gating intentionally deferred to a follow-up issue (#76). - Default-deny
access_controlrule on themainfirewall: every route now requiresIS_AUTHENTICATED_FULLYexcept thePUBLIC_ACCESSallow-list (/login,/logout,/register,/register/pending). Anonymous visitors hitting a gated route get the standard Symfony redirect to/login, with the originally requested URL preserved so they land back on the page after signing in (#97). - Authenticated-user dropdown menu in the top nav. The user's display name is the trigger; the menu groups into a Bruger section (Edit profile, Log out) and a role-gated Administration section (Administrér organisationer, Administrér brugere). Each item is only rendered when both the acting user holds the gating role and the target route is registered, so the menu degrades gracefully on branches where later admin PRs haven't landed yet. ARIA "Menu Button" pattern via a new Stimulus controller — Escape and outside-click close the menu (#108).
- Shared admin-list Table Twig component family
(
templates/components/Table.html.twig+templates/components/Table/Head|Body|Row|HeadCell|Cell.html.twig) that renders a semantic<table>inside a horizontal-scroll wrapper, with zebra-striped body rows and analignprop on the cell components for right-flushed action columns. First consumer migrations land alongside their respective PRs (#112). - Anonymous self-signup at
/register. The route is open to unauthenticated visitors; submissions go throughApp\Security\Registrationwhich validates the email format, checks the right-hand-side domain against an env-backed allow-list (REGISTRATION_ALLOWED_EMAIL_DOMAINS, comma-separated; default empty so production must opt-in by setting the var, withexample.testset in.env.testfor the test suite), requires matching password confirmation, and creates theUserwithstatus = Pending. The user is redirected to/register/pending("thanks, awaiting approval") and cannot sign in until a domain manager approves them. CSRF-protected via Symfony'scsrf_token('register')helper. Localised in the existingmessagesdomain (#62). - Admin user-management surface at
/admin/usersper ADR 006. Lists users scoped by role —ROLE_ADMINsees every user, aROLE_DOMAIN_MANAGERsees only users whose email domain matches their own. Optional?status=pending|approved|blockedfilter for the approval queue (/admin/users/pendingredirects to?status=pending). Per-row Approve / Block buttons are gated by theManageUserVoterfrom #84 (same-domain check) and the newApp\Security\UserApprovalservice flips the status. The list view uses a new repository finderUserRepository::findVisibleTo(), scoped against the actor's role + email domain via theEmailDomainhelper. CSRF-protected;backparameter on the action forms only honours/admin/users…URLs (#64, #85). ROLE_DOMAIN_MANAGER+ROLE_ADMINrole identifiers (App\Security\Roles),role_hierarchywiring insecurity.yamlsoROLE_ADMINimpliesROLE_DOMAIN_MANAGER, and a domain-scopedManageUserVoterthat grants theMANAGE_USER/APPROVE_USER/BLOCK_USERattributes when the acting user is a domain manager in the subject's email domain (or a site-wide admin). Lays the authorisation foundation for the admin approval queue (#64) and the scoped user-management list view (#85) per ADR 006 (#84).User.name(display name) andUser.status(lifecycle enum:pending | approved | blocked) per ADR 006, plus theApp\Enum\UserStatusPHP enum.UserManager::createUser()now requiresnameand accepts an optionalstatus(defaultApprovedfor the console / fixture path; the registration flow in #62 will passPending). Theapp:user:createconsole command takes a thirdnameargument; fixtures seed Alice + Bob with display names. Schema is added via a single migration that backfills any existing rows withname = ''andstatus = 'approved'(#45, #83).- Added tailwind build to site-install task.
- Shared
HeadingTwig component (templates/components/Heading.html.twig) that renders<h1>–<h6>with size tokens centralised in one place. Refactorsassistant/show.html.twig,security/login.html.twig, and thePageHeader,Hero,EmptyState,Filter/Railcomponents to use it (#92). App\Security\AccountStatusCheckerimplementingUserCheckerInterface— gates the login flow so anyUserwhosestatusis notApprovedis rejected before the password is verified, with distinct localised messages per state (account.awaiting_email_confirmation,account.pending,account.blocked) rendered in thesecuritytranslation domain. Wired on themainfirewall viasecurity.yaml'suser_checker:key (#63, #103).- Shared
DescriptionListTwig component family (templates/components/DescriptionList/List.html.twig+templates/components/DescriptionList/Item.html.twig) for label/value pairs. The assistant detail's runtime attribute grid adopts it (#94). - ADR
005-organization-entityrecording the decision to introduceOrganizationas a first-class entity with name, multiple emails, and a default framework — no language-model field, with theUser → Organizationreference and admin CRUD tracked as separate issues (#65). OrganizationDoctrine entity (name, list of email domains, default framework), repository, migration, andOrganizationFixturesseeding three baseline kommuner (Aarhus, Aalborg, Odense). First step of ADR 005 —User → Organization, CRUD, and assistant autocomplete land in follow-up issues (#75).User.name(display name) andUser.status(UserStatusenum:awaiting_email_confirmation | pending | approved | blocked) fields (#45, #83, #103).ROLE_DOMAIN_MANAGER+ROLE_ADMINrole identifiers (App\Security\Roles),role_hierarchywiring insecurity.yamlsoROLE_ADMINimpliesROLE_DOMAIN_MANAGER, and a domain-scopedManageUserVoterthat grants theMANAGE_USER/APPROVE_USER/BLOCK_USERattributes when the acting user is a domain manager in the subject's email domain (or a site-wide admin). (#84).- Test-env
framework.exceptionsoverride soNotFoundHttpExceptionlogs atinfoinstead oferror, keeping PHPUnit output clean when a test deliberately asserts a 404 (#95). - Shared
AlertTwig component (templates/components/Alert.html.twig) for flash messages and inline errors.type(success|error|warning|info) drives the ARIA role; the login error block adopts it (#93). - Catalogue listing page with filters (#15).
- Initial Symfony 8 application scaffold on the ITK Dev Docker
symfony-8template (phpfpm 8.4, nginx, MariaDB, Mailpit, Traefik), including dev dependencies for coding standards (php-cs-fixer,twig-cs-fixer) and composer normalization (#1). - Architecture Decision Records under
docs/adr/with index and the first ADR001-tech-stack-docker-symfony(#11). CLAUDE.mdwith project-level operating instructions for AI agents — stack, structure, execution policy, branching, commits, CHANGELOG, ADR conventions, translations, brand env vars, Tailwind rebuild notes, and the domain glossary (#5).- Human-facing
README.mdrewritten around the AI Bibliotek catalog — project description, status banner, feature list, tech stack, Task-based local development workflow, contributing pointers, and prototype references (#28). CONTRIBUTING.mddocumenting branching, Conventional Commits, coding standards, changelog expectations, and the pull-request workflow (#9).- Project license declared as MPL-2.0 — full
LICENSEtext at the repo root,composer.jsonlicensefield updated fromproprietarytoMPL-2.0, and ADR004-project-license-mpl-2recording the rationale (#32). Taskfile.ymlexposing common developer commands viatask --list(compose helpers, composer, console, coding-standards family) with README updates documentingtaskas a host requirement (#29).- Frontend tooling: Tailwind CSS via
symfonycasts/tailwind-bundle, Symfony AssetMapper, and Stimulus viasymfony/stimulus-bundle, with base Twig layout (templates/base.html.twig), asset entrypoints (assets/app.js,assets/styles/app.css), Tailwind v4 design tokens (@theme), and ADR002-frontend-tooling(#38). - PHPUnit test harness with a 100 % coverage gate enforced in CI via
rregeer/phpunit-coverage-check(#31). - README refocused as human-facing project documentation: project purpose,
tech stack, and local development bootstrap. Developer command reference
moved to
CLAUDE.md(and laterCONTRIBUTING.md, tracked in #9). - ITK Dev Docker setup via the
symfony-8template (phpfpm 8.4, nginx, MariaDB, Mailpit). - Dev dependencies for coding standards and composer normalization:
ergebnis/composer-normalize,friendsofphp/php-cs-fixer,vincentlanglet/twig-cs-fixer. - Project README with local development instructions.
- Frontend tooling: Tailwind CSS (via
symfonycasts/tailwind-bundle), Symfony AssetMapper, and Stimulus (viasymfony/stimulus-bundle). Decision recorded in ADR 002. (#14, #16). - Base Twig layout (
templates/base.html.twig) and frontend asset entrypoints (assets/app.js,assets/styles/app.css). - Placeholder frontpage at
/(App\Controller\FrontpageController) previewing the AI Bibliotek design with hardcoded sample data (hero, search box, sample-assistant rail, "Sådan virker det" steps), site chrome (header with brand + nav, footer), the Stimulusnav_toggle_controllerdriving the mobile menu, and ablock-on-labelGitHub Action providing a per-PR merge gate (#40). - User authentication:
UserDoctrine entity (email, hashed password, roles),UserRepository(withPasswordUpgraderInterface), theUserManagerservice that hides persistence + hashing, form-login firewall +/login+/logout, fixtures for two baseline users (alice@example.test,bob@example.test— passwordpassword), console commandsapp:user:createandapp:user:change-password, and end-to-end functional + unit tests (#2). - PHPUnit suite split into
unit(no database) andintegration(full kernel) testsuites undertests/Unit/andtests/Integration/, with transactional database isolation per integration test viadama/doctrine-test-bundle. The integration suite uses a dedicatedtests/bootstrap_integration.phpthat builds the schema from ORM metadata and loads baselineUserFixturesonce before any test; DAMA's per-test transaction rolls back mutations so the baseline persists. The defaulttests/bootstrap.phpis minimal and is used bytask test-unit.task test-unitandtask test-integrationexpose the suites individually. - Reusable Twig form components under
templates/components/Form/:Form/Label,Form/Input, andForm/Button(withvariantandsizeprops for future styling variants). The/logintemplate consumes them instead of inlining the input/label/button markup. - Site chrome (header with brand + nav, footer) in
templates/base.html.twig, with the Fraunces/Geist font stack preloaded from Google Fonts. - Tailwind v4 design tokens (
@themeinassets/styles/app.css) matching the prototype palette and typography. - Stimulus controller
nav_toggle_controllerdriving the mobile navigation menu. - GitHub Action
block-on-labelthat fails the check while ado-not-mergelabel is applied to a pull request, providing a per-PR merge gate for dependencies (e.g. another PR that must land first). LICENSEfile at repo root containing the full Mozilla Public License 2.0 text.- Project license declared as MPL-2.0 (Mozilla Public License 2.0); the
licensefield incomposer.jsonupdated from the Symfony skeleton defaultproprietaryto the SPDX identifierMPL-2.0. Taskfile.ymlexposing common developer commands viatask --list(compose helpers, composer, console, coding-standards family).- README documents Task as a host requirement and
uses
tasktargets in the Common commands section. docs/adr/with index (docs/adr/README.md) and ADR001-tech-stack-docker-symfonydocumenting the choice of Symfony 8 on the ITK Dev Dockersymfony-8template.CONTRIBUTING.mddocumenting branching, Conventional Commits, coding standards, changelog expectations and the pull-request workflow.- GitHub issue template
.github/ISSUE_TEMPLATE/issue.mdand pull-request template.github/PULL_REQUEST_TEMPLATE.md, each with a human-facing "Resume" / checklist section followed by an "AI specificities" detail block so other agents can continue work from a structured brief (#69).