Skip to content

feat/frontend-thymeleaf-templates - #85

Merged
ebouchut merged 6 commits into
devfrom
feat/frontend-thymeleaf-templates
Jul 11, 2026
Merged

feat/frontend-thymeleaf-templates#85
ebouchut merged 6 commits into
devfrom
feat/frontend-thymeleaf-templates

Conversation

@ebouchut

@ebouchut ebouchut commented Jul 11, 2026

Copy link
Copy Markdown
Owner

This PR applies the mockup design to the application.
The five Thymeleaf pages now render the Catppuccin theme with the accessibility wiring designed in docs/design/.

  • Layout fragments (fragments/layout.html, plain th:fragment): skip link, landmarks, and an auth-aware navigation (sec:authorize switches anonymous links vs Dashboard + logout POST; aria-current="page" driven per page). The chrome is marked lang="en" so it stays correctly identified on the French privacy page.
  • Design tokens served: the mockup stylesheets move into static/css/:
    • Catppuccin is the default,
    • Soft Paper (unused for now).
  • Self-hosted webfonts (closes feat(frontend): Self-host the webfonts (GDPR: no Google Fonts requests) #78): Atkinson Hyperlegible, Sora, and JetBrains Mono as local woff2 + @font-face, OFL notices included; zero requests to Google (GDPR: LG Muenchen I, 3 O 17493/20).
  • Register error state, server-driven: role="alert" summary, per-field aria-invalid + aria-describedby wired to the th:errors spans; hints mirror the real Bean Validation bounds.
  • RGAA: new docs/rgaa.md (definition, DWWM expectations, 19-criteria map with how/where, verification method), tracked by epic 🔵 RGAA (Accessibility) #84.

Bug caught during browser verification: calling #fields.hasAnyErrors() outside the th:object scope made GET /auth/register 500 disguised as a redirect to login (the error page sits behind authentication).
Fixed by moving th:object to <main>, and add a new test that renders the form and its invalid-submission re-render (the gap: the flow test only POSTed valid data).

Verified:
- All tests passed, and all pages rendered light and dark,
- Fonts 200 from /fonts/** with no fonts.googleapis.com traffic,
- Live error state shows computed aria-describedby="username-hint username-error" (and none on valid fields),
- Landmarks present,
- Mobile 375px reflows cleanly.

Closes #78

ebouchut added 6 commits July 10, 2026 16:44
Copy the mockup stylesheets into the served static resources: base.css
(structural tokens and BEM components), theme-catppuccin.css (default
theme, Catppuccin Latte light and Mocha dark via prefers-color-scheme),
and theme-soft-paper.css (dormant alternate defining the same token
names). The mockups under docs/design/ remain the design source of truth.
Serve Atkinson Hyperlegible (400, 700), Sora (600, 700), and JetBrains
Mono (400) as woff2 files from the application itself, declared in
fonts.css with font-display swap, and allow anonymous access to /fonts/**.

Loading these from Google Fonts would transmit every visitor's IP address
to Google before any consent, which LG Muenchen I (20 Jan 2022,
3 O 17493/20) held to violate the GDPR. All three families are SIL OFL
licensed, which permits self-hosting; the license texts ship next to the
font files as the OFL requires.

Fixes #78
Add templates/fragments/layout.html with three plain Thymeleaf fragments
(no layout dialect dependency): head(title) with the stylesheet links,
header(current) with the skip link and an authentication-aware navigation
(sec:authorize switches between the anonymous links and the Dashboard plus
logout POST form; the current parameter drives aria-current=page), and the
site footer carrying the privacy policy and source links. The chrome is
marked lang=en so it stays correctly identified on the French privacy page.
Convert the five templates to the mockup markup, keeping the live
Thymeleaf wiring:

- home: hero with accent heading, decorative aria-hidden code card,
  feature cards; CTA buttons link to register and login.
- login: narrow form card with labelled, autocompleted fields; the flash
  states become semantic alerts (registered and logout as role=status,
  error as role=alert), replacing the inline color style.
- register: full server-driven error state: a role=alert summary when the
  form has errors, and per field a conditional form__input--invalid class,
  aria-invalid=true, and aria-describedby pointing at the hint and the
  th:errors span. Hints mirror the real Bean Validation constraints
  (username 3 to 50, password 8 to 100). Autocomplete uses new-password.
- dashboard: authenticated header from the fragment (logout POST moved
  there), welcome heading with sec:authentication, honest empty state for
  courses until the course domain exists.
- privacy: wrapped with the shared head, header, and footer fragments,
  content unchanged and still lang=fr.

The per-page privacy links added earlier are superseded by the footer
fragment, which carries the policy link on every page.
Add docs/rgaa.md: what the RGAA is (the French application of WCAG 2.1 AA,
106 criteria in 13 themes), what the DWWM certification expects, how
accessibility is built into learn-dev by construction (WCAG-checked tokens,
mockups encoding the wiring, shared layout fragments), a criteria map
giving for each implemented criterion the requirement, the how, and the
where (clickable file links), and the verification method with the manual
passes still to run.

Tracked by the RGAA epic issue #84. Linked from the README documentation
list and from the CONTRIBUTING design expectations.
The happy-path flow test never rendered the register template: a template
exception on GET /auth/register surfaced as a redirect to the login page
(the error page sits behind authentication), not as a visible failure.
Add a test that renders the empty form and the invalid-submission
re-render, asserting the alert, aria-invalid, and the field error id are
present in the response body.
@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.68%. Comparing base (96c13a4) to head (82854ce).

Additional details and impacted files
@@             Coverage Diff              @@
##                dev      #85      +/-   ##
============================================
+ Coverage     76.13%   80.68%   +4.54%     
- Complexity       24       26       +2     
============================================
  Files             9        9              
  Lines            88       88              
  Branches          5        5              
============================================
+ Hits             67       71       +4     
+ Misses           16       13       -3     
+ Partials          5        4       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ebouchut ebouchut self-assigned this Jul 11, 2026
@ebouchut ebouchut added documentation Improvements or additions to documentation feature legal Legal topics, such as licensing, GDPR... frontend labels Jul 11, 2026
@ebouchut ebouchut moved this to In Progress in learn-dev-project Jul 11, 2026
@ebouchut ebouchut moved this from In Progress to In Review in learn-dev-project Jul 11, 2026
@ebouchut ebouchut added this to the v0.6 - Frontend milestone Jul 11, 2026
@ebouchut ebouchut linked an issue Jul 11, 2026 that may be closed by this pull request
4 tasks
@ebouchut
ebouchut merged commit 6f9206e into dev Jul 11, 2026
7 checks passed
@ebouchut
ebouchut deleted the feat/frontend-thymeleaf-templates branch July 11, 2026 16:00
@github-project-automation github-project-automation Bot moved this from In Review to Done in learn-dev-project Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation feature frontend legal Legal topics, such as licensing, GDPR...

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

feat(frontend): Self-host the webfonts (GDPR: no Google Fonts requests)

1 participant