Context
I'm using Zensical tags with per-tag brand icons on a personal documentation site: most identifiers get their own icon (simple/html5, simple/javascript, simple/css, simple/rust, ...), and unmapped tags should fall back to the default icon (lucide/hash). Setup follows https://zensical.org/docs/setup/tags/#tag-icons-and-identifiers.
Reproduced on Zensical 0.0.50 and 0.0.57 (latest at time of reporting).
Bug description
When [project.theme.icon.tag] contains both default and an identifier that sorts alphabetically directly before "default" (e.g. css, but also api, bug, ...), the generated tag-icon CSS contains the default icon under the previous identifier's selector, overriding that identifier's own icon. The affected tag then renders with the wrong (default) icon.
Concretely, the build output contains two rules for the same selector, where the second one wins:
.md-tag.md-tag--css { --md-tag-icon: url('... simple/css ...'); } /* correct */
.md-tag.md-tag--css { --md-tag-icon: url('... lucide/hash ...'); } /* leaked "default", overrides the above */
Root cause: in zensical/ui, src/partials/icons.html (same code in dist/partials/icons.html), modifier is only assigned when type != "default":
{% if type != "default" %}
{% set modifier = ".md-tag--" ~ type %}
{% endif %}
In Jinja2, {% set %} inside a {% for %} body persists across iterations, so when the loop reaches "default" it reuses the previous iteration's selector. Since config.theme.icon.tag is iterated in alphabetical order, exactly the identifier preceding "default" is clobbered. If default sorts first, the bug does not manifest.
Workaround (what I use for now): override the partial via custom_dir with a fixed template that resets modifier = "" at the start of each iteration — or omit the default entry entirely (unmapped tags then show no icon).
Related links
Reproduction
Minimal reproduction attached as bug-report-repro.zip (2 files, well below 1 MB):
zensical.toml
[project]
site_name = "Tag icon default bug repro"
[project.theme.icon.tag]
default = "lucide/hash"
css = "simple/css"
[project.extra.tags]
CSS = "css"
docs/index.md
---
tags:
- CSS
---
# Tag icon bug reproduction
No customizations are involved (no custom_dir, extra_css, or extra_javascript).
bug-report-repro.zip
Steps to reproduce
- Unzip the reproduction and open the folder in a terminal.
- Install Zensical and run
zensical build.
- Open the generated
site/index.html and locate the <style>:root{...} block near the top.
- Note that there are two
.md-tag.md-tag--css{...} rules: the first embeds the CSS logo (simple/css), the second embeds lucide/hash.
- Open the page in a browser (or just look at the CSS cascade): because both rules share the same specificity, the second rule wins and the
CSS tag shows the hash icon instead of the CSS logo.
Expected: the default entry emits the plain .md-tag{...} selector, so only tags without an associated identifier get the default icon.
Browser
No response
Before submitting
Context
I'm using Zensical tags with per-tag brand icons on a personal documentation site: most identifiers get their own icon (
simple/html5,simple/javascript,simple/css,simple/rust, ...), and unmapped tags should fall back to the default icon (lucide/hash). Setup follows https://zensical.org/docs/setup/tags/#tag-icons-and-identifiers.Reproduced on Zensical 0.0.50 and 0.0.57 (latest at time of reporting).
Bug description
When
[project.theme.icon.tag]contains bothdefaultand an identifier that sorts alphabetically directly before"default"(e.g.css, but alsoapi,bug, ...), the generated tag-icon CSS contains the default icon under the previous identifier's selector, overriding that identifier's own icon. The affected tag then renders with the wrong (default) icon.Concretely, the build output contains two rules for the same selector, where the second one wins:
Root cause: in
zensical/ui,src/partials/icons.html(same code indist/partials/icons.html),modifieris only assigned whentype != "default":In Jinja2,
{% set %}inside a{% for %}body persists across iterations, so when the loop reaches"default"it reuses the previous iteration's selector. Sinceconfig.theme.icon.tagis iterated in alphabetical order, exactly the identifier preceding"default"is clobbered. Ifdefaultsorts first, the bug does not manifest.Workaround (what I use for now): override the partial via
custom_dirwith a fixed template that resetsmodifier = ""at the start of each iteration — or omit thedefaultentry entirely (unmapped tags then show no icon).Related links
zensical/ui/src/partials/icons.htmltheme.icon.tag,tag icons,md-tag-icon,default tag iconReproduction
Minimal reproduction attached as
bug-report-repro.zip(2 files, well below 1 MB):zensical.tomldocs/index.mdNo customizations are involved (no
custom_dir,extra_css, orextra_javascript).bug-report-repro.zip
Steps to reproduce
zensical build.site/index.htmland locate the<style>:root{...}block near the top..md-tag.md-tag--css{...}rules: the first embeds the CSS logo (simple/css), the second embedslucide/hash.CSStag shows the hash icon instead of the CSS logo.Expected: the
defaultentry emits the plain.md-tag{...}selector, so only tags without an associated identifier get the default icon.Browser
No response
Before submitting