Skip to content

attrs passthrough: explicit attrs={...} dict is silently discarded by JinjaX's real Catalog #78

Description

@fsecada01

Background

Discovered while implementing #76 (Select/Textarea/FormField attrs rollout), fixing a crash in the pre-existing test_form_field_endpoint_renders_input integration test. It affects Button too (#72/#73) — this is not specific to the three primitives #76 added, just newly surfaced because FormField already had a real-Catalog integration test and Button didn't.

The bug

JinjaX reserves the prop name attrs for its own extra-kwargs collector (jinjax/catalog.py, ARGS_ATTRS = "attrs") and unconditionally overwrites whatever a component's own {#def} declares for attrs, on every render — regardless of what the caller passed.

Two distinct consequences fall out of this:

  1. Bare extra kwargs work correctly. catalog.render("Cf:Select", data_event="submit") — JinjaX auto-collects data_event into an HTMLAttrs object, which (after Roll out attrs passthrough to Select, Textarea, FormField #76's fix) flows through cf-ui's render_attrs() validation/escaping/collision-guard correctly. This is the live, working pattern for FastAPI/Litestar consumers.
  2. An explicit attrs={...} dict keyword is silently discarded. catalog.render("Cf:Select", attrs={"data-event": "submit"}) — the exact syntax docs/primitives.md documents (:attrs="{...}" in JinjaX/cotton usage) — produces no error and no output. JinjaX's Catalog layer never lets the dict reach the component at all; render_attrs()'s current hasattr(attrs, "as_dict") unwrap only handles the case where JinjaX does hand it an HTMLAttrs object (the bare-kwargs path) — it never sees the caller's explicit dict to unwrap in the first place.

Reproduction

Verified against a real JinjaX Catalog (via install_cf_ui):

catalog.render("Cf:Button", content="Save", attrs={"data-event": "submit", "hx-post": "/orders"})
# -> <button class="button" type="button">Save</button>
# data-event / hx-post silently dropped, no error

A collision attempt via the same path (attrs={"id": "override"}) is also silently swallowed rather than raising PrimitiveConfigError — the guard never fires because the dict never reaches render_attrs().

Impact

Every documented :attrs="{...}" JinjaX usage in docs/primitives.md currently no-ops silently under a real Catalog, for every primitive that has adopted attrs (button, select, textarea, form-field). The bare-kwargs pattern works and should probably become the documented/recommended one, but the discrepancy between documented and actual behavior needs resolving either way (fix the reservation collision, or fix the docs).

Suggested approach

Needs investigation into whether JinjaX exposes any way to opt a specific prop out of the ARGS_ATTRS reservation, or whether cf-ui needs to rename its prop (e.g. extra_attrs) to avoid the collision entirely — mirroring how class was already renamed to extra_class for a similar reserved-word collision. A rename is a breaking change for the already-shipped Button.attrs API, so this needs a deliberate decision, not a quick patch.

Related: #70, #72, #73, #76

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions