Five icons draw without their geometry: the root's size stripped from every element #85

Closed
opened 2026-09-07 13:49:55 +00:00 by tiagoagueda · 0 comments
Owner

Observation

the email icon on the left bar is unreadable, it shows only "V" not the full icon

and, a minute later:

i noticed the same behaviour on the icons languages and dashboard

What is wrong

_icon_source in core/templatetags/postulo.py strips Lucide's fixed 24-pixel size and its
own class, so that one file can serve a 16-pixel glyph and a 48-pixel illustration:

source = re.sub(r'\s+(?:width|height|class)="[^"]*"', "", source)

That runs over the whole file, not the root element. Lucide draws an envelope as a flap
and a box:

<path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" />
<rect x="2" y="4" width="20" height="16" rx="2" />

and the rendered markup came out as:

<path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" /><rect x="2" y="4" rx="2" />

On a <rect>, width and height are not a size — they are the shape. Without them the
rectangle has no extent and draws nothing, which leaves the flap alone: a "V".

How many

Five of the thirty-nine bundled icons have geometry on a child element, and every one of
them has been drawing wrongly since the tag was written:

Icon Where it shows What was lost
mail Server settings → Email the envelope, leaving a "V"
calendar Settings → Language and time the month box, leaving the two hanging rings
layout-dashboard Settings → Dashboard every panel; the icon is entirely empty
monitor Server settings → Overview the screen, leaving a bare stand
briefcase the main navigation the case, leaving the handle

Three of the five are in the settings sidebar, side by side, which is why they were noticed
together.

Why nothing caught it

tests/test_icons.py checked that the tag produces an inline <svg>, that it is sized by
CSS, hidden from assistive technology, that the names match assets/icons.txt, and that
path traversal is refused. Every one of those is about the root element. Nothing looked
at what was inside it, so an icon that rendered as a valid, well-labelled, correctly sized,
empty box passed.

Nor could the accessibility suite help: the icons are aria-hidden="true" — correctly, they
sit beside their own words — so axe has nothing to say about them, and a screen reader reads
the sidebar perfectly either way. This was only ever going to be found by a person looking
at it.

The fix

Strip the root and nothing else:

_ROOT_SVG = re.compile(r"<svg\b[^>]*>")
source = re.sub(_ROOT_SVG, _without_size_and_class, source, count=1)

Three tests come with it: each of the five icons keeps a child's width and height, each
still loses the root's, and the mail icon draws both a flap and an envelope — that last one
stating the reported symptom as itself. The assertions match the attribute rather than the
substring, since stroke-width="2" contains width= and must survive.

Classification

Bug. Cosmetic in consequence, but it had been shipping in every release and in the container
image, and the one place it could be caught was somebody's eyes.

## Observation > the email icon on the left bar is unreadable, it shows only "V" not the full icon and, a minute later: > i noticed the same behaviour on the icons languages and dashboard ## What is wrong `_icon_source` in `core/templatetags/postulo.py` strips Lucide's fixed 24-pixel size and its own class, so that one file can serve a 16-pixel glyph and a 48-pixel illustration: ```python source = re.sub(r'\s+(?:width|height|class)="[^"]*"', "", source) ``` That runs over **the whole file**, not the root element. Lucide draws an envelope as a flap and a box: ```xml <path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" /> <rect x="2" y="4" width="20" height="16" rx="2" /> ``` and the rendered markup came out as: ```xml <path d="m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" /><rect x="2" y="4" rx="2" /> ``` On a `<rect>`, width and height are not a size — they are the shape. Without them the rectangle has no extent and draws nothing, which leaves the flap alone: a "V". ## How many Five of the thirty-nine bundled icons have geometry on a child element, and every one of them has been drawing wrongly since the tag was written: | Icon | Where it shows | What was lost | | --- | --- | --- | | `mail` | Server settings → Email | the envelope, leaving a "V" | | `calendar` | Settings → Language and time | the month box, leaving the two hanging rings | | `layout-dashboard` | Settings → Dashboard | every panel; the icon is entirely empty | | `monitor` | Server settings → Overview | the screen, leaving a bare stand | | `briefcase` | the main navigation | the case, leaving the handle | Three of the five are in the settings sidebar, side by side, which is why they were noticed together. ## Why nothing caught it `tests/test_icons.py` checked that the tag produces an inline `<svg>`, that it is sized by CSS, hidden from assistive technology, that the names match `assets/icons.txt`, and that path traversal is refused. Every one of those is about the **root element**. Nothing looked at what was inside it, so an icon that rendered as a valid, well-labelled, correctly sized, empty box passed. Nor could the accessibility suite help: the icons are `aria-hidden="true"` — correctly, they sit beside their own words — so axe has nothing to say about them, and a screen reader reads the sidebar perfectly either way. This was only ever going to be found by a person looking at it. ## The fix Strip the root and nothing else: ```python _ROOT_SVG = re.compile(r"<svg\b[^>]*>") source = re.sub(_ROOT_SVG, _without_size_and_class, source, count=1) ``` Three tests come with it: each of the five icons keeps a child's width and height, each still loses the root's, and the mail icon draws both a flap and an envelope — that last one stating the reported symptom as itself. The assertions match the attribute rather than the substring, since `stroke-width="2"` contains `width=` and must survive. ## Classification Bug. Cosmetic in consequence, but it had been shipping in every release and in the container image, and the one place it could be caught was somebody's eyes.
tiagoagueda added this to the 0.3.0 milestone 2026-09-07 13:49:55 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Postulo/postulo#85
No description provided.