Skip to content

feat: Add custom-style support to Djehuty - #275

Merged
kairoaraujo merged 25 commits into
4TUResearchData:mainfrom
kikisprdx:wip-feat-147-split-up-style-loading
Sep 1, 2026
Merged

feat: Add custom-style support to Djehuty#275
kairoaraujo merged 25 commits into
4TUResearchData:mainfrom
kikisprdx:wip-feat-147-split-up-style-loading

Conversation

@kikisprdx

@kikisprdx kikisprdx commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

Add support for loading custom fonts, CSS stylesheets and static
assets from Djehuty's JSON configuration.

Changes

  • src/djehuty/web/config/json_parser.py: Support repeated JSON definitions
    through findall.
  • src/djehuty/web/config/runtime.py: Store custom font and stylesheet
    configuration at runtime.
  • src/djehuty/web/ui.py: Read font and stylesheet configuration and validate
    configured asset paths.
  • src/djehuty/web/wsgi.py: Serve generated font CSS and configured theme
    assets.
  • src/djehuty/web/resources/html_templates/fonts.css: Generate font-face
    declarations and typography variables.
  • src/djehuty/web/resources/html_templates/layout.html: Load generated font
    CSS and instance-specific stylesheets.
  • src/djehuty/web/resources/static/css/main.css: Apply configurable
    typography variables.
  • etc/djehuty/djehuty-dev-config.json: Add empty custom-style
    configuration fields.
  • etc/djehuty/djehuty-example-config.json: Add empty custom-style
    configuration fields.
  • etc/djehuty/djehuty-example-config.xml: Add empty custom-style
    configuration fields and a font-face definition.
  • tests/unit/: Test JSON parsing, theme configuration, asset validation and
    generated CSS.
  • tests/e2e/tests/test_smoke.py: Test that the generated font CSS is served and
    linked from portal pages.

Approval Checklist

  • I agree to follow Djehuty's code of conduct.
  • I have read and I have follow the code contribution workflow.
  • Code style and conventions were respected.
  • Documentation has been updated where needed (README, docs, or examples).
  • Review approved by at least one maintainer.
  • Merge readiness (PR is squashed into a single commit and follows the commit template).

Issue Reference (optional - PRs may not be associated with an issue)

Closes #237

Screenshots (optional)

Notes (optional)

I changed read_custom_stylesheets to ignore blank custom stylesheet entries in the config. See 404fd8e. I changed read_custom_stylesheets to ignore blank custom stylesheet entries and added a warning for configured assets without a source (f799fd9). All other code from @gabrielakuhn remains unchanged.

All example configs have fields defined to demonstrate usage. The djehuty-dev-config.json also has fields for consistency.

@kikisprdx kikisprdx changed the title Wip feat 147 split up style loading feat: Add custom-style support to Djehuty Aug 19, 2026
@kikisprdx
kikisprdx force-pushed the wip-feat-147-split-up-style-loading branch from abf9e3e to 50a8ab3 Compare August 19, 2026 10:54
@kikisprdx
kikisprdx requested a review from 641e16 August 19, 2026 10:56
@kikisprdx
kikisprdx marked this pull request as ready for review August 19, 2026 10:57
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 44.21769% with 246 lines in your changes missing coverage. Please review.
✅ Project coverage is 18.45%. Comparing base (d7a83be) to head (049be35).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
src/djehuty/web/ui.py 43.12% 228 Missing and 12 partials ⚠️
src/djehuty/web/wsgi.py 25.00% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #275      +/-   ##
==========================================
+ Coverage   18.10%   18.45%   +0.34%     
==========================================
  Files          21       21              
  Lines       10521    10587      +66     
  Branches     2040     2059      +19     
==========================================
+ Hits         1905     1954      +49     
- Misses       8422     8434      +12     
- Partials      194      199       +5     

☔ 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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kairoaraujo

Copy link
Copy Markdown
Collaborator

@kikisprdx, can you fix the conflict please :)

gabrielakuhn and others added 18 commits August 27, 2026 09:52
* src/djehuty/web/resources/static/css/main.css: Add CSS variables for the
  default fonts, and use them in font-family declarations.

(cherry picked from commit 59c0d6a)
Signed-off-by: Kikis <kikispcmr@gmail.com>
* src/djehuty/web/config.py: Add fonts attribute as a default None.
* src/djehuty/web/ui.py: Add read_fonts_configuration to parse the optional
  <fonts> block.
* src/djehuty/web/wsgi.py: Add fonts.css route and fonts_css handler,
  and write fonts.css in create_static_error_pages.
* src/djehuty/web/resources/html_templates/fonts.css: Add CSS font template.
* src/djehuty/web/resources/html_templates/layout.html: Link fonts.css
  after main.css so instance typography overrides the defaults.

(cherry picked from commit 212ead1)
Signed-off-by: Kikis <kikispcmr@gmail.com>
* etc/djehuty/djehuty-dev-config.json: Add fonts configuration.
* src/djehuty/web/config/json_parser.py: Add method to support
  repeated config entries (e.g. multiple fonts).

(cherry picked from commit 6e3cc21)
Signed-off-by: Kikis <kikispcmr@gmail.com>
Custom footers are inserted as raw HTML through the config file,
so an inline <style> cannot carry a nonce and is refused by the
security policy. Instances can now point at a stylesheet
served from their own assets root.

* src/djehuty/web/config/runtime.py: Hold the configured stylesheets.
* src/djehuty/web/ui.py: Read the stylesheets from the config,
  and warn on start when a font or a css cannot be resolved.
* src/djehuty/web/wsgi.py: Pass the stylesheets on to the templates.
* src/djehuty/web/resources/html_templates/layout.html: Include the
  stylesheets after the defaults so an instance can override them.
* src/djehuty/web/config.py: Remove unecessary code.
* etc/djehuty/djehuty-dev-config.json: Serve the development fonts from
  the assets root.

(cherry picked from commit 710256f)
Signed-off-by: Kikis <kikispcmr@gmail.com>
* etc/djehuty/djehuty-dev-config.json: Add path for custom favicon

(cherry picked from commit 2f56761)
Signed-off-by: Kikis <kikispcmr@gmail.com>
* etc/djehuty/djehuty-dev-config.json: Load the instance stylesheets, and
  set the footer to the dark grey.

(cherry picked from commit cd02345)
Signed-off-by: Kikis <kikispcmr@gmail.com>
* src/djehuty/web/resources/html_templates/portal.html: Add margin botton for
  the new-notice.

(cherry picked from commit 5a4c411)
Signed-off-by: Kikis <kikispcmr@gmail.com>
* etc/djehuty/djehuty-dev-config.json: Update the development config to include
  the new chip and shape styles.

(cherry picked from commit 926beba)
Signed-off-by: Kikis <kikispcmr@gmail.com>
* etc/djehuty/djehuty-dev-config.json: Load the new stylesheet.

(cherry picked from commit c15b216)
Signed-off-by: Kikis <kikispcmr@gmail.com>
* src/djehuty/web/resources/html_templates/public_metadata.html: Remove the
  dots from the Export as.

(cherry picked from commit 19e3e60)
Signed-off-by: Kikis <kikispcmr@gmail.com>
* src/djehuty/web/resources/html_templates/dataset.html: Write the data
  headings in sentence case, so the wording no longer carries the styling.

(cherry picked from commit 70759dd)
Signed-off-by: Kikis <kikispcmr@gmail.com>
* src/djehuty/web/resources/html_templates/portal.html: Rename categories with an
  ampersand.

(cherry picked from commit a902abe)
Signed-off-by: Kikis <kikispcmr@gmail.com>
* src/djehuty/web/resources/html_templates/colors.css: Increasing space
  between files and button.

(cherry picked from commit d1f604e)
Signed-off-by: Kikis <kikispcmr@gmail.com>
  * src/djehuty/web/ui.py: Ignore empty custom stylesheet
  paths.

Signed-off-by: Kikis <kikispcmr@gmail.com>
  * tests/unit/test_json_clean_schema.py: Test repeated JSON configuration
    values through `findall`.
  * tests/unit/test_config_parser.py: Test font and stylesheet configuration
    parsing and asset validation.
  * tests/unit/test_theme_css.py: Test generated font stylesheet rendering.
  * tests/e2e/tests/test_smoke.py: Test that the generated font stylesheet
    is served and linked from portal pages.

Signed-off-by: Kikis <kikispcmr@gmail.com>
…alues.

*  etc/djehuty/djehuty-dev-config.json: Add placeholder config examples.
*  etc/djehuty/djehuty-example-config.json: Add placeholder config fields as
   examples.
*  etc/djehuty/djehuty-example-config.xml: Add placeholder config fields as
   examples.
*  src/djehuty/web/ui.py: Add guard for the case that no source is found.
   Protects under the case of defined CSS variables but empty values.

Signed-off-by: Kikis <kikispcmr@gmail.com>
  * tests/e2e/tests/test_smoke.py: Stop requiring a font-face declaration when
    no fonts are configured.
    `TestFontsCss::test_renders_font_faces_and_variables` covers rendered font.
  * tests/unit/test_config_parser.py: Fix PEP-8 formatting.
  * tests/unit/test_theme_css.py: Fix PEP-8 formatting.

Signed-off-by: Kikis <kikispcmr@gmail.com>
* tests/e2e/tests/test_citation.py: Alter test to assert against the new UI text
  "Export as", which previously was "Export as...".

Signed-off-by: Kikis <kikispcmr@gmail.com>
@kikisprdx
kikisprdx force-pushed the wip-feat-147-split-up-style-loading branch from 50a8ab3 to b251948 Compare August 27, 2026 08:11

@641e16 641e16 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for splitting this out, the splitting is done well! Unit tests plus an e2e check for the generated stylesheet is also super nice!

There are a few things implementation wise that I think should be fixed before a merge:

  • fonts.css values are HTML-escaped.
  • mono-font-family does nothing; Nothing in the codebase reads var(--font-mono).
  • assets_root is None misses "", which is what both example configs ship and reports a misleaing message.
  • The theme-CSS tests can't catch the first bullet point issue.
  • Docs not included. In general I feel like docs for a feature or an implementation as such should be shipped together in one PR. just a nit.

Some other nits I saw:

  • I don't think the new findall() handles nested paths nor the [@attr='value'] predicates that find() supports, returning [] rather than erroring.
  • --font-ui is read by one selector (#primary-menu li a), which is narrower than the name suggests.

One note for future PRs:
src/djehuty/web/ui.py isn't in the [tool.ruff] include list, so running just format over it produced a lot of changes which makes it hard to find the actual feature code. Either keep formatting to the paths already in the include list or put reformats in their own commits. Otherwise, great work!

Comment on lines 57 to 58
#new-notice { margin-top: 0em; margin-bottom: 40pt }
</style>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this missing a ;?

Suggested change
#new-notice { margin-top: 0em; margin-bottom: 40pt }
</style>
#new-notice { margin-top: 0em; margin-bottom: 40pt; }
</style>

otherwise, although these change the UI slightly, these seem as generic UI polishes so I think good to keep in this PR and not part of some specific seperate branding.

Comment thread src/djehuty/web/ui.py
try:
from onelogin.saml2.auth import OneLogin_Saml2_Auth # pylint: disable=unused-import
from onelogin.saml2.errors import OneLogin_Saml2_Error # pylint: disable=unused-import

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the ui.py formatting: no need to change anything in this PR now, but a note for future ones. src/djehuty/web/ui.py isn't in the [tool.ruff] include list in pyproject.toml yet (its a draft PR #185), so running just format over it produces a big formatting diff that isn't enforced afterwards. That makes the lines of actual feature code hard to find during review, and it doesn't stick (e.g., the recent rebase already pulled old-style call sites back in from main), so the file is currently a mix of both styles and ruff format --check still flags it.

For future PRs: either keep formatting to the paths already in the include list, or put the reformat in its own commit so the feature diff stays readable. Preferably option 1.

"privilege-button-color": "#fce3bf",
"footer-background-color": "#707070"
},
"fonts": {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it would be helpful to have an actual filled in example for the config?

Same applies to the xml exmaple file.

Comment thread tests/unit/test_theme_css.py Outdated
Comment on lines +13 to +14
environment = Environment(loader=FileSystemLoader(TEMPLATES_DIR))
template = environment.get_template("fonts.css")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This builds its own Environment without autoescape, so it renders the template under different rules than the server does (wsgi.py:440 passes autoescape=True). The assertions below pass on output that production never produces, which is how the escaping issue in fonts.css got through. Worth having the test construct its environment the same way WebServer does.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, this was a good catch!

Comment on lines +4 to +5
font-family: '{{face.family}}';
src: url('{{face.src}}') format('{{face.format}}');

@641e16 641e16 Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These values go through Jinja's HTML autoescaping, because the environment that renders this template is built with autoescape=True (wsgi.py:440). CSS doesn't understand HTML entities, so a family name containing an apostrophe renders as font-family: 'Bob's Sans' - valid CSS syntax naming a font that doesn't exist, so it never loads. Also any src with a query string gets its & turned into &, changing the URL that's actually requested.

Adding | safe is not going to fix it. The three --font-* variables below already have it, and --font-body is broken too: an apostrophe in an admin-supplied value ends the CSS string early and the browser discards the whole declaration. Family is being template-quoted while body_font is admin-quoted.

On a dev instance: setting "family": "Bob's Sans" and "body-font-family": "'Bob's Sans', sans-serif" (restart the container) and check http://localhost:8080/theme/fonts.css :

@font-face {
    font-family: 'Bob&#39;s Sans';
    src: url('/assets/f.woff2?v=1&amp;x=2') format('woff2');
    font-display: swap;
}
:root {
    --font-body: 'Bob's Sans', sans-serif;
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good catch! This is an issue impacting all CSS config options, but for hex colours, we never had to deal with this possibility.

Comment on lines +32 to +33
--font-mono: 'FiraMono', monospace;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An admin who sets "mono-font-family": "'JetBrains Mono', monospace" gets the value parsed correctly, served correctly, and sitting in their browser correctly but nothing on the page changes. This is because nothing seems to read --font-mono; the places that do render monospace text name the font directly instead of going through the variable. In portal.html:11, admin/exploratory.html:19 and :27 all say font-family: 'FiraMono', monospace;.

Because the default --font-mono: 'FiraMono', monospace happens to match what the hardcoded rules already use, the site looks correct. The gap only shows up when someone tries to change it which is the one thing the feature exists to allow from what I understand.

Comment thread src/djehuty/web/ui.py Outdated
Comment on lines +708 to +732
def read_fonts_configuration(xml_root):
"""Procedure to parse and set the custom typography configuration."""
fonts = xml_root.find("fonts")
if fonts is None:
return

font_faces = []
for face in fonts.findall("font-face"):
font_faces.append(
{
"family": config_value(face, "family"),
"src": config_value(face, "src"),
"format": config_value(face, "format", fallback="woff2"),
"weight": config_value(face, "weight"),
"style": config_value(face, "style"),
"display": config_value(face, "display", fallback="swap"),
}
)

config.fonts = {
"font_faces": font_faces,
"body_font": config_value(fonts, "body-font-family"),
"ui_font": config_value(fonts, "ui-font-family"),
"mono_font": config_value(fonts, "mono-font-family"),
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No fallback= anywhere; config.fonts = {...} doesn't update config.fonts, it throws the old one away and puts a new dictionary in its place. Anything this file didn't specify becomes None, and font_faces becomes an empty list if this file has no font-face entries. Give config.fonts a real default in runtime.py, then merge per key like colours (function above: read_colors_configuration) does. You need to also decide if qhen a second file has entries, should they be added to the parent's list, or replace it? this is a design choice.

Maybe also drop incomplete faces in read_fonts_configuration rather than in the template, so the validator and the renderer agree on what is valid, and identify entries by index in the warning so the message is actionable.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to have no fallback. It would be easier to spot when a font rule isn't working properly. In the case of multiple files, default behavior would just use the latest definition, which I think makes sense for configuration files. I agree with the validator change, it would make troubleshooting easier.

Comment thread src/djehuty/web/ui.py Outdated
Comment on lines +755 to +757
if assets_root is None:
logger.warning("%s refers to '%s' but 'custom-assets-root' is unset.", description, source)
elif not os.path.isfile(os.path.join(assets_root, source[len(prefix) :])):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is None misses the empty-string case, so this disagrees with the check at line 1146. config_value returns "" (not None) for "custom-assets-root": "", which is what both djehuty-example-config.json and djehuty-dev-config.json use.

Comment on lines +25 to +35
"family": "Example Sans",
"src": "/assets/fonts/example-sans.woff2",
"format": "woff2",
"weight": "400 700",
"style": "normal",
"display": "swap",
},
],
"body_font": "'Example Sans', sans-serif",
"ui_font": "'Example Sans', sans-serif",
"mono_font": "'Example Mono', monospace",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn't a single character in there that HTML escaping would touch. No apostrophe, no ampersand, no angle bracket. Escaping "Example Sans" gives you back "Example Sans". Also could be how the escaping issue got through.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add some new regressions tests for this.

* etc/djehuty/djehuty-example-config.json: Add placeholder font values.
* etc/djehuty/djehuty-example-config.xml: Add placeholder font values.

Signed-off-by: Kikis <kikispcmr@gmail.com>
* src/djehuty/utils/convenience.py: Add new `css_string` filter, which escapes
  apostrophes and back slashes.
* src/djehuty/web/resources/html_templates/fonts.css: Pipe css variables through
  `css_string`.
* src/djehuty/web/wsgi.py: Register `css_string` as a Jinja filter.
* tests/unit/test_theme_css.py: Register `css_string` as a Jinja filter for
  tests.

Signed-off-by: Kikis <kikispcmr@gmail.com>
… missing

font paths with more specific logging warnings.

* src/djehuty/web/resources/html_templates/admin/exploratory.html: Replace
  hardcoded font with css variable.
* src/djehuty/web/resources/html_templates/portal.html: Replace hardcoded font
  with css variable, and minor CSS formatting fixes.
* src/djehuty/web/ui.py: Warn about an unresolvable asset when the assets root
  is empty (not just unset). Drop `font-face` entries missing `family` or `src`
  during config parsing instead of letting the template render invalid CSS for
  them and identify font entries by index in warnings.

Signed-off-by: Kikis <kikispcmr@gmail.com>
  * tests/unit/test_config_parser.py: Pass `logger` through to
    `read_fonts_configuration`; add coverage for the empty-`assets_root`
    warning and for dropping incomplete font-face entries.
  * tests/unit/test_theme_css.py: Add coverage asserting font values are not
    HTML-escaped, since `fonts.css` output is CSS, not HTML.

Signed-off-by: Kikis <kikispcmr@gmail.com>
@kikisprdx

Copy link
Copy Markdown
Collaborator Author

Thank you for splitting this out, the splitting is done well! Unit tests plus an e2e check for the generated stylesheet is also super nice!

There are a few things implementation wise that I think should be fixed before a merge:

* fonts.css values are HTML-escaped.

* mono-font-family does nothing; Nothing in the codebase reads var(--font-mono).

* assets_root is None misses "", which is what both example configs ship and reports a misleaing message.

* The theme-CSS tests can't catch the first bullet point issue.

* Docs not included. In general I feel like docs for a feature or an implementation as such should be shipped together in one PR. just a nit.

Some other nits I saw:

* I don't think the new findall() handles nested paths nor the [@attr='value'] predicates that find() supports, returning [] rather than erroring.

* --font-ui is read by one selector (#primary-menu li a), which is narrower than the name suggests.

One note for future PRs: src/djehuty/web/ui.py isn't in the [tool.ruff] include list, so running just format over it produced a lot of changes which makes it hard to find the actual feature code. Either keep formatting to the paths already in the include list or put reformats in their own commits. Otherwise, great work!

Thank you for your review and good eye! I've solved the outstanding issues. A few points I think would be appropriate for a future refactor. Matching the findall function to the XML nested pattern is a larger task that I think dilutes this PR. --font-ui should be used in more places, I agree, but this can be part of a larger overhaul to the frontend. Currently, neither of these nits introduces bugs.

@kikisprdx
kikisprdx requested a review from kairoaraujo August 31, 2026 10:58
@kairoaraujo

Copy link
Copy Markdown
Collaborator

I've checked the latest changes, it seems good.
@641e16 do you want to do another round on this PR?

kairoaraujo
kairoaraujo previously approved these changes Aug 31, 2026
@kikisprdx
kikisprdx requested a review from 641e16 August 31, 2026 14:20
@641e16

641e16 commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

I've checked the latest changes, it seems good. @641e16 do you want to do another round on this PR?

Hi @kairoaraujo, yes I will do a quick final round today :)

@641e16 641e16 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working through these. I've checked the fixes and they all do what they say, good job!

A few new things slipped through in with these commits so please have a look!

Comment on lines +47 to +53
"custom-assets-root": "/etc/djehuty/assets",
"custom-stylesheet": [
"/assets/css/palette.css",
"/assets/css/buttons.css"
],
"custom-logo-path": "/etc/djehuty/assets/images/logo.png",
"custom-favicon-path": "/etc/djehuty/assets/images/favicon.ico",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These paths don't exist, and the example config is the file people copy to get started.

Copying this config would result in your site losing its logo and favicon with no explanation.

The old empty-string values had their own problem, but that's fixed in the code now. I'd suggest leaving these keys out of the example entirely and letting the docs in #283 carry the worked example. Or perhaps you can add comments to these lines to indicate the logo and favicon should be added to these directories for the template to work as necessary. Same applies to the XML version.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we don't have anything to supply as default variables (as default styling is part of the source code) I think the current approach should be clear. I'll add comments to make it clearer these are placeholders.

Comment thread src/djehuty/web/ui.py Outdated
Comment on lines +778 to +779
for index, face in enumerate(config.fonts["font_faces"]):
warn_about_unresolvable_asset(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These index numbers don't match the ones in the drop warning, because this counts the filtered list while line 716 counts the original one.

With a bad face first and a good one second:

WARNING: Dropping font-face #0: 'family' and 'src' are both required.
WARNING: Font 'Good' (font-face #0) refers to '/assets/fonts/good.woff2' which does not exist under '...'

Two different entries both reported as #0. Since the number is there to tell entries apart, it's misleading when there are several with problems. Keeping the original index on each face when it's built would sort it out I think..

Comment thread tests/unit/test_theme_css.py Outdated
"display": None,
},
],
"body_font": "'O'Brien & Sons', sans-serif",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This value is broken CSS, and the assertion on line 103 makes it seem like this is the expected output. Using a properly escaped stack like 'O\'Brien & Sons', sans-serif tests exactly the same behaviour..

The family value here is correct though -> the template supplies the quotes, css_string escapes the apostrophe, and font-family: 'O'Brien & Sons'; is correct.

body_font on line 95 is the one I'd change. That field the admin must writee the quotes themselves and it goes through bare | safe which means 'O'Brien & Sons', sans-serif is a malformed value: the string closes after 'O' and the browser discards the declaration. Line 103 then asserts that broken output is expected.

The assertion is checking that we don't HTML-escape the stack, and a correctly-quoted value proves that just as well. the & is still unescaped, so the & guard keeps working:

so something like:
"body_font": "'O\'Brien & Sons', sans-serif",
assert "--font-body: 'O\'Brien & Sons', sans-serif;" in css

Comment thread src/djehuty/utils/convenience.py Outdated

def css_string(value):
"""Escape VALUE for safe interpolation into a single-quoted CSS string."""
return str(value).replace("\\", "\\\\").replace("'", "\\'")

@641e16 641e16 Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth also handling newlines here. A CSS string can't span lines, and config_value returns element text without stripping it, so ordinary XML formatting like:

<family>
  Example Sans
</family>

renders a CSS string with real line breaks in it, which the browser won't parse. Either strip the value or escape \n as \A. read_custom_stylesheets already strips its entries, so it would be nice to follow the same pattern here.

…iltering

fonts.

* src/djehuty/utils/convenience.py: Replace new lines with an escape sequence.
* src/djehuty/web/ui.py: Strip whitespace from config values. Store index, and
  re-use it for logging.

Signed-off-by: Kikis <kikispcmr@gmail.com>
…ixes.

* tests/unit/test_config_parser.py: Cover config_value stripping leading and
  trailing whitespace from XML text nodes.
* tests/unit/test_convenience.py: Cover css_string escaping backslashes,
  quotes, and new lines.
* tests/unit/test_theme_css.py: Fix body_font test value to use a
  double-quoted CSS string instead of requiring backslash-escaping.
* tests/unit/test_theme_css.py: Add linter suggestion. Wrap in `'` to avoid
  escaping.

Signed-off-by: Kikis <kikispcmr@gmail.com>
@kikisprdx
kikisprdx force-pushed the wip-feat-147-split-up-style-loading branch from 8fcac3c to 930bf32 Compare August 31, 2026 16:28
* tests/unit/test_config_parser.py: Add index to dictionaries in
  `test_reads_font_faces_and_font_families`.

Signed-off-by: Kikis <kikispcmr@gmail.com>

@641e16 641e16 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your changes :) Looks good now!

@kairoaraujo
kairoaraujo merged commit 0823985 into 4TUResearchData:main Sep 1, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[NEW FEATURE]: Support custom-style in Djehuty

4 participants