feat: base assistant detail page (#20)#68
Merged
Conversation
Adds App\Controller\AssistantController with a single show action mapped at GET /assistant/{id}. Symfony's MapEntity converter resolves the route id to an Assistant (404 on unknown id). The Twig template renders the base fields the entity currently carries: title, description, framework + language model in a runtime box, and tags in a tag box when non-empty. Uses the existing Eyebrow / Box components and the Danish translation file (security pattern: keys under assistant.detail.*).
Out of scope per the entity's current shape: export entry point (#22), system prompt preview, parameters, organisation / author display, back-link to a real catalogue (#15) — back currently points at the frontpage. These hook in as the underlying data lands.
Tests: 38 cases, 100 assertions, 100% coverage. WebTestCase exercises the happy path with tags, the no-tags variant (asserts the tags section is omitted), and the 404 path. Depends on PR #67 (base Assistant entity) which depends on PR #59.
Refs #20, #16.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Controllers stay thin per project conventions — the type signatures, attribute, and route name already describe what the action does. The descriptive blurb belonged on the (still-thin) service layer if anywhere; with no service yet, deleting reads cleaner than keeping placeholder docs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an explicit paragraph under 'Controllers stay thin' so the convention demonstrated by stripping AssistantController's docblocks doesn't get re-added by a future session. The class name, route attribute, types, and return all carry the same information a class- or method-level docblock would — explanatory prose belongs on the service the controller delegates to. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AssistantFixtures loads twenty assistants per doctrine:fixtures:load. Five are hand-written entries lifted from the AI Bibliotek prototype (Borgerservice-vejviser, Mødereferent, Journaliseringsassistent, Skole- og dagtilbudssvar, Tilsynsrapport-assistent) with detailed Danish descriptions and authentic kommune attribution in the description text. The remaining fifteen are generated from a fixed cartesian product of seven topics × ten kommuner × five language models, indexed by loop counter modulo each dimension — no randomness, so the same seed produces the same catalogue every run. Per ADR 005 the kommune attribution is woven into the description for now; it migrates to the Organization relation when that lands. Tests: 40 cases, 136 assertions, 100% coverage. Verifies the count, that the first five are the named authentic entries, that every generated row has a unique (title, description) signature and carries the kommune in the title, that the language model rotation reaches all five values, and that a second run on a fresh schema produces byte-for-byte identical titles. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
FrontpageController drops its SAMPLE_ASSISTANTS const and injects AssistantRepository. The rail iterates the five most-recently created Assistant rows (id DESC) and each card now links to /assistant/{id}. Stats: Assistanter and Sprogmodeller are computed via repository queries (count(\*), count distinct languageModel via a new repository helper); Kommuner stays a placeholder constant 10 until ADR 005 / #65's Organization entity lands. PHPDoc removed from the controller per the project convention.
Template: the existing CardRail:Card props (kommune / model / name / summary) are mapped to the Assistant entity's framework / languageModel / title / description for now; the kommune slot returns to a real organisation name once Organization lands.
Tests: 42 cases, 149 assertions, 100% coverage. FrontpageControllerTest now uses the schema-reset trait and covers three paths: empty DB still renders (with no card links), persisted assistants are linked by id and newest-first order is preserved, and the Assistanter + Sprogmodeller stats reflect the catalogue's actual count and distinct-LM cardinality.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Base automatically changed from
feature/issue-14-assistant-base-entity
to
develop
June 17, 2026 08:28
tuj
previously approved these changes
Jun 17, 2026
tuj
left a comment
Contributor
There was a problem hiding this comment.
Rename kommune to municipality
4 tasks
5d760d2 to
6d7ae68
Compare
1e4ad91 to
7c434cc
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Links to issues
(delivers the name + description + tags + model + framework
half; remaining scope deferred — see the AI specificities block).
(base
Assistantentity), which depends onPR #59 (Doctrine +
PHPUnit).
Description
Adds a base assistant detail page at
GET /assistant/{id}plus thefixture baseline the rest of the integration suite now reads from.
Application code
App\Controller\AssistantController::show— single action, routeapp_assistant_show. Symfony'sMapEntityconverter resolves{id}→Assistantand 404s on a missing row.templates/assistant/show.html.twig— extendsbase.html.twig,uses the existing
Eyebrow/Boxcomponents, and reads from theassistant.detail.*translation keys added under the existingsecuritypattern intranslations/messages.da.yaml.App\Controller\FrontpageController+templates/frontpage/index.html.twig— rail and stats blocks now consume real assistant data so the
frontpage reflects the seeded catalogue (rail shows the five
newest entries id-DESC; stats show total assistants and distinct
language-model count).
Fixtures
App\DataFixtures\AssistantFixturesseeds 21 deterministicassistants: six hand-written rows (five authentic prototype
entries plus the tagless
Uden kategorieredge-case row used bythe controller's empty-tags branch) and fifteen generated from a
fixed grid of seven topics × ten kommuner × five language models.
No randomness — same input every run.
tests/bootstrap_integration.phpnow loadsAssistantFixturesalongside
UserFixturesonce per suite. DAMA's PHPUnit extensionwraps each test in a transaction that rolls back at tearDown, so
per-test mutations stay isolated while the baseline survives the
whole run.
Tests
tests/Unit/DataFixtures/AssistantFixturesTest.php— pureTestCasemirroringUserFixturesTest; mocksObjectManager,captures
persist()calls, asserts the 6 + 15 layout, thetagless row's empty
tags, unique (title, description)signatures for the generated entries, the full five-model
rotation, and determinism across two
load()invocations.tests/Integration/Repository/AssistantRepositoryTest.php—resolves the repo from the container and round-trips a
persist/find.tests/Integration/Controller/AssistantControllerTest.php—three cases driven entirely off the fixture baseline (no ad-hoc
new Assistant): happy path with tags(
Borgerservice-vejviser), empty-tags branch via theUden kategorierrow, and 404 for an unknown id.tests/Integration/Controller/FrontpageControllerTest.php—fixture-only. Derives the expected top-5 rail entries via
AssistantRepository::findBy([], ['id' => 'DESC'], 5)so theassertions stay correct if the seed grows, and pins the stats
<dl>to the fixture totals (21 / 5).Screenshot of the result
(Optional — screenshot the detail page and the updated rail/stats
block if useful for the reviewer.)
Checklist
If your code does not pass all the requirements on the checklist you have to add a comment explaining why this change
should be exempt from the list.
Additional comments or questions
do-not-mergeis intentional — this PR sits on top of #67 (whichsits on top of #59). Per the CLAUDE.md
do-not-mergerule theblocker is the dependency chain; remove the label once both
ancestors land.
Details - AI specificities
Goal and motivation
parameters, system prompt, and an export entry point on a
per-assistant detail page. The base entity from feat: add base Assistant entity (#14) #67 only carries
six fields, so this PR delivers the half that has data today and
defers the rest to follow-ups that each unblock a chunk.
stats blocks needed real catalogue data to render meaningfully,
and the integration tests for both controllers wanted a shared,
deterministic baseline rather than ad-hoc
new Assistantconstructions in every
setUp().Scope
repository.
AssistantFixturesseed (6 + 15 = 21 entries) with one taglessedge-case row.
tests/bootstrap_integration.phploads the assistant baselinealongside
UserFixtures.AssistantFixturesTest) andtwo integration tests refactored to read from the baseline
(
AssistantControllerTest,FrontpageControllerTest), plusAssistantRepositoryTestfor the repository round-trip.Non-goals / deferred
(richer scope).
#65.
currently links back to the frontpage as a placeholder.
loaded, so the no-assistants case is no longer reachable in the
integration suite. If empty-state coverage matters later it
belongs as a unit test of the rail template, not at the
controller-HTTP layer.
Conventions applied
UserFixtures(registered as aservice, loaded by
tests/bootstrap_integration.phponce persuite, isolated per-test by DAMA's
enable_static_connectionconfiguration in
config/packages/dama_doctrine_test_bundle.yaml).tests/Unit/DataFixtures/UserFixturesTest.php(pure
TestCase, mockedObjectManager, capturespersistcallbacks).
new Assistant(...)/$em->persist/$em->flush. Test datais found by title (
findOneBy(['title' => …])) so a fixturereshuffle that preserves titles doesn't break the tests.
Areas needing scrutiny
FrontpageControllerTestderives the expected ordering from the same query the controller
uses (
findBy([], ['id' => 'DESC'], 5)). That keeps itresilient to fixture growth but means the test won't catch a
regression where the controller silently changes the order — a
later snapshot-style test could add that.
<dl>is keyed to21(total) and5(distinct languagemodels). Reseeding
AssistantFixturesrequires updating thisassertion; the test's comment names the five LMs to make the
brittleness explicit.
Related
the Organization entity that the detail page will eventually
surface (author / domain).