feat: adopt Tailwind + AssetMapper + Stimulus as frontend stack#41
Merged
Conversation
Acts on #38 per @yepzdk's recommendation: lean on the defaults Symfony provides. Installs `symfony/asset-mapper`, `symfony/asset`, `symfony/twig-pack`, `symfony/stimulus-bundle`, and `symfonycasts/tailwind-bundle`. Tailwind v4 is driven by the bundle's managed binary, so the project stays Node-free. Decision recorded in `docs/adr/002-frontend-tooling.md`. Refs #38 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a header comment so the next reader knows the file was generated by the `symfony/stimulus-bundle` Flex recipe, what double-submit cookie CSRF protection it implements, and how Flex behaves on update. Refs #38 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
Contributor
|
Looks good to me. |
Run `composer normalize` after the frontend tooling additions. Tweaks the version constraint formatting (`8.1.*` → `~8.1.0`, `^2.12|^3.0` → `^2.12 || ^3.0`) and refreshes the lock file's content hash. Refs #38 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8 tasks
tuj
previously approved these changes
Jun 10, 2026
…v/ai-lib into feature/issue-38-frontend-tooling
martinyde
added a commit
that referenced
this pull request
Jun 11, 2026
* feat: placeholder frontpage + do-not-merge label workflow Adds a thin `FrontpageController` mounted at `GET /` that renders a placeholder Twig template, identifying the project as ai-lib and signalling that the application is under construction. The template extends `templates/base.html.twig` from PR #41 and styles itself with Tailwind utilities — the asset pipeline introduced there is its first real consumer. A `WebTestCase` smoke test asserts `/` returns 200 and the response contains "ai-lib". The test file is committed ahead of #31; it will start running automatically once PHPUnit lands. Also adds `.github/workflows/block-on-label.yaml`: a per-PR merge gate that fails the check while a `do-not-merge` label is applied. Useful when a PR depends on another PR/release/review that needs to land first. The workflow only fails the check — making the merge button itself unavailable requires marking this check as a required status check in branch protection (out of scope for this PR). Refs #40 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: frontpage previews AI Bibliotek design with sample data Evolves the placeholder frontpage from a single "under construction" message into a faithful preview of the AI Bibliotek prototype, so visitors get a first-glance impression of the catalogue before any real data exists. Mirrors the home view from `itk-dev/research-projects/docs/public/projects/ai-bibliotek/mocks`: - Hero with eyebrow, headline, lede paragraph, and three-stat block (assistant / kommune / model counts driven by the sample data). - Non-functional search prompt. - Horizontal rail of five sample assistants drawn from the prototype's seed data (Borgerservice-vejviser, Mødereferent, Journaliserings- assistent, Skole- og dagtilbudssvar, Tilsynsrapport-assistent). - "Sådan virker det" four-step ordered list. - "Kommer snart" chip grid for teased future features. The prototype's CSS is **converted to Tailwind v4 utilities** rather than imported verbatim. Design tokens (palette, fonts) move into `@theme` blocks in `assets/styles/app.css`, so they become Tailwind variants (`bg-surface`, `text-ink`, `font-display`, etc.). A small `@layer components` block keeps the pseudo-element and keyframe patterns that utility classes can't express (`eyebrow::before`, fade-up stagger, hamburger toggle). `templates/base.html.twig` gets the prototype's chrome — header with brand mark + responsive nav, footer, Fraunces/Geist fonts preloaded from Google Fonts. A new `nav_toggle_controller` Stimulus controller drives the mobile menu. Sample data lives in `FrontpageController` as `const SAMPLE_ASSISTANTS` — intentionally inline so the day this is replaced by a real `AssistantRepository`, the diff is small and obvious. Refs #40 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: split frontpage into anonymous Twig components Install symfony/ux-twig-component and refactor the placeholder frontpage and base layout into 17 anonymous components under templates/components/ (Layout, Nav, Hero, Stats, SearchBox, CardRail, Box, StepList, TagList, Eyebrow). Rendered HTML and Stimulus / CSS hooks are unchanged; future sections can compose the same primitives instead of duplicating utility lists. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: emit dev-mode HTML comments around every Twig template Wrap each template's output in begin + end HTML comments that print the template path via Twig's _self variable. Gated on app.environment == 'dev' so prod output is unchanged. Makes it trivial to identify which template produced any DOM region from browser DevTools. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: drop Kommer snart section from frontpage Removes the Coming Soon chip list from templates/frontpage/index.html.twig and the now-unused COMING_SOON const + template variable from FrontpageController. The TagList components stay in templates/components/ as reusable primitives. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: add project Twig component conventions to CLAUDE.md Documents the anonymous-component-first structure under templates/components/, the {% props %} + named-slot contract, the dev-mode {{ _self }} begin/end markers, and when to extract vs. inline. Intended as the reference future contributors (and future Claude sessions) read before adding new templates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Added twig node visitor * feat: add Danish translations + brand env vars Install symfony/translation with default locale da and a single translations/messages.da.yaml file holding every user-facing label, ARIA string, placeholder, page title, and copy block. Components translate text props internally via |trans, so call sites pass keys (e.g. label="frontpage.stats.assistants") rather than literal Danish text. The hero heading and footer caption use _html-suffixed keys rendered with |raw. Brand identity (name, tagline, initials) is sourced from BRAND_* environment variables, exposed as Twig globals (brand_name, brand_tagline, brand_initials) with parameter-backed defaults in config/services.yaml so the page works whether or not .env defines them. Brand strings are configuration, not localization, and are not translated. CLAUDE.md documents the new Translations and Brand-identity sections. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: card hover top border + stats stacking on narrow viewports CardRail container: add pt-2 so the card's -translate-y-0.5 lift and its shadow-md aren't clipped by overflow-y:auto (which the spec forces on us via overflow-x:auto). Stats list: switch grid-cols-3 to grid-cols-1 sm:grid-cols-3 so the long single-word Danish labels (SPROGMODELLER) get their own row below the sm breakpoint instead of overflowing the column and pushing the page horizontally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: ship brand identity defaults in .env Adds BRAND_NAME, BRAND_TAGLINE, BRAND_INITIALS as committed defaults so the parameter fallbacks in config/services.yaml are only a safety net. .env.local can override per-deployment. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: note Tailwind rebuild gotcha There is no live watcher and cache:clear does not rebuild the stylesheet — new utility classes only apply after tailwind:build runs. CLAUDE.md gets a dedicated subsection so future sessions stop being puzzled; README.md gets a heads-up callout next to the build commands. Also fixes a stale hello_controller.js reference in README. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: stub footer links to # until destinations are decided Drops the hardcoded github.com/itk-dev/ai-lib and prototype-mockup URLs from base.html.twig so the placeholder frontpage doesn't ship pointers we haven't committed to yet. Both footer link href and the %link% substitution in the caption become '#' and can be wired to real targets (env var or route) later. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: cards wrap into rows instead of horizontal scroll The CardRail was a horizontal snap-scroll rail. Its flex children with min-w-[260px] forced the rail's min-content to ~1364px, which propagated up through the section into the view-root's implicit grid column and dragged the entire page horizontally on narrow viewports. Switching the container to 'flex flex-wrap gap-4' and the card to 'grow basis-[260px] max-w-[400px]' lets cards reflow onto new rows as the viewport narrows. Also adds grid-cols-1 to view-root so its implicit column gets minmax(0, 1fr) and no future overflow-x child triggers the same propagation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor: promote section eyebrows to <h2>, card titles to <h3> Eyebrow gains an 'as' prop defaulting to h2 — section kickers are now real subheadings under the hero's h1. Hero overrides with as="p" because its eyebrow sits above the h1 and isn't a heading. Card titles become h3, completing the h1 -> h2 -> h3 hierarchy. To keep the visual identical, .eyebrow declares font-family: var(--font-sans) (the @layer base h1..h4 rule would otherwise force the display serif onto a <h2 class="eyebrow">), and the card title gets tracking-normal to cancel the -0.01em letter-spacing applied to every heading by that same base rule. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: fix yaml-lint, composer-normalized, and phpunit checks Three failing PR checks addressed: - yaml-lint: translations/messages.da.yaml reformatted with Prettier. - composer-normalized: pin symfony/translation as ~8.1.0 instead of 8.1.* so composer normalize stops rewriting it on every CI run; composer.lock refreshed. - PHPUnit + coverage gate: assets/styles/app.css imports tailwindcss via Tailwind v4's directive, which AssetMapper can't resolve until bin/console tailwind:build has run. The Tests workflow now runs tailwind:build after composer install and before phpunit, mirroring the local-dev flow documented in README/CLAUDE.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(changelog): drop test-related entries as baseline noise PHPUnit harness and the frontpage smoke test stay in the codebase; having tests is the default expectation for the project, not a noteworthy user-facing change. Drops the two related bullets from the [Unreleased] block. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Added tests for nodevisitor * Added tests for nodevisitor * Reverted CHANGELOG change * Reverted a deleted method in Kernel * Added Kernel test * refactor: .env is the only source of BRAND_* defaults Drops the brand.default_* parameters in config/services.yaml and the corresponding default: fallbacks in the Twig globals. .env already ships the BRAND_NAME/BRAND_TAGLINE/BRAND_INITIALS values, so the parameter-backed safety net just duplicated that source and would silently drift if one side changed without the other. Addresses tuj's review on PR #43. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(a11y): SearchBox label describes the field, not the submit action The <label> and the input's aria-label both echoed submitLabel ("Søg"), so the field's accessible name duplicated the submit button rather than describing the input. The placeholder isn't a substitute for a label — it disappears on input and has weak screen-reader support. Use legend ("Find en assistent") for the <label> and drop the now-redundant aria-label. Addresses yepzdk's review on PR #43. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Updated .env --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: martinyde <yde001@gmail.com>
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.
Summary
Acts on the comments on #38. @yepzdk suggested Tailwind + Symfony
AssetMapper + Vanilla JS / Stimulus, on the grounds that the project
should lean on the defaults Symfony provides. This PR adopts that stack
and records the decision in an ADR.
docs/adr/002-frontend-tooling.mddocumenting the choice andthe options that were considered.
symfony/asset-mapper,symfony/asset,symfony/twig-pack,symfony/stimulus-bundle, andsymfonycasts/tailwind-bundle. Recipesapplied (config/packages, importmap, base layout, stimulus bootstrap).
package.json, nonode_modules, no Node container in CI orproduction.
assets/app.cssplaceholder withassets/styles/app.css(
@import "tailwindcss";).assets/app.jsnow boots Stimulus via thegenerated
stimulus_bootstrap.js.README.mdwith a Frontend assets section (install /build / watch / inspect commands) and adds an
[Unreleased] / Addedentry to
CHANGELOG.md.Scope notes
Add Taskfile to the project #29 / chore: add Taskfile with project commands #35). The Taskfile itself is not yet in
main; this PRdocuments the raw
itkdev-docker-compose php bin/console …commandsin the README. Wrapping them in
tasktargets can be added togetherwith Add Taskfile to the project #29 / chore: add Taskfile with project commands #35.
JS load. The base layout (
templates/base.html.twig) is wired andthe pipeline compiles cleanly (
bin/console tailwind:buildproducesvar/tailwind/app.built.css;bin/console debug:asset-mapliststhe registered assets). The first consuming route (
GET /) isintentionally left to Implement simple frontpage with placeholder content #40 (frontpage placeholder) to avoid
pre-empting that issue's controller scaffolding.
002on the assumption that the ADRstructure PR (Set up ADR (Architecture Decision Records) structure #11 → 001-tech-stack-docker-symfony) lands first. If
Set up ADR (Architecture Decision Records) structure #11 lands after this PR, the numbering will need a trivial rebase.
Test plan
itkdev-docker-compose composer installsucceeds against thenew lock file.
itkdev-docker-compose php bin/console tailwind:buildproducesvar/tailwind/app.built.csswithout errors.itkdev-docker-compose php bin/console debug:asset-maplistsapp,styles/app.css, and the stimulus controllers.itkdev-docker-compose vendor/bin/php-cs-fixer fix --dry-run→0 fixable files.
itkdev-docker-compose vendor/bin/twig-cs-fixer lint→ OK.docker compose --profile dev run --rm prettier '**/*.{yml,yaml,css,js}' --check→ OK.docker compose --profile dev run --rm markdownlint markdownlint 'docs/**/*.md' 'README.md' 'CHANGELOG.md'→ OK.closes #38
🤖 Generated with Claude Code