Skip to content

fix(docs): document JinjaX _attrs= for attrs passthrough (#78) - #81

Merged
fsecada01 merged 2 commits into
masterfrom
fix/component-framework-ui-phase-78-jinjax-attrs-docs
Aug 20, 2026
Merged

fix(docs): document JinjaX _attrs= for attrs passthrough (#78)#81
fsecada01 merged 2 commits into
masterfrom
fix/component-framework-ui-phase-78-jinjax-attrs-docs

Conversation

@fsecada01

Copy link
Copy Markdown
Owner

Summary

  • docs/primitives.md documented <Cf:Button :attrs="{...}"> for JinjaX usage. JinjaX reserves the prop name attrs for its own extra-kwargs collector and unconditionally overwrites whatever a component's {#def} declares for it — that syntax compiles and runs but silently discards the dict under a real Catalog, with no error. Fixed the docs to show :_attrs="{...}" instead, JinjaX's own escape hatch for this exact collision, and explained why in a callout.
  • Added tests/integration/test_jinja_attrs_passthrough.py, against a real jinjax.Catalog (mirroring test_jinja_autoescape.py's pattern). The existing unit-tier attrs tests never caught this bug: without a real Catalog, {#def} is a plain Jinja2 comment, so attrs never actually collides with anything there — only a real Catalog reproduces it, which is why these live at the integration tier.
  • Investigating this surfaced a second, narrower gap: the RESERVED_ATTRS collision guard (PrimitiveConfigError) only reliably fires when the colliding key is not also one of the component's own declared prop names. For a key that's both (type/href for Button; name for the form controls), JinjaX's own arg-filtering routes it straight into that prop before render_attrs ever runs — the guard never fires, and the caller's real prop (if also passed) silently wins instead. This is a JinjaX-level limitation, not a cf-ui bug to patch around; documented explicitly in both the docs and the test module's docstring, and locked in with a test so a future JinjaX version change would be caught rather than assumed away.

Test plan

  • pytest tests/integration/test_jinja_attrs_passthrough.py -v — 6/6 pass: bare kwargs passthrough, _attrs= dict passthrough, attrs= silently discarded (pins the bug), reserved-undeclared-name collision still raises, reserved-declared-name collision silently bypasses the guard (documented limitation), hostile value still escaped
  • pytest tests/unit -q — 2826 passed, 13 skipped
  • pytest tests/integration -q — 31 passed (25 existing + 6 new)
  • mkdocs build --strict — clean
  • ruff check / ruff format --check — clean

🤖 Generated with Claude Code

https://claude.ai/code/session_01Ur2c6d3peuerUGgRdJ6Fvs

fsecada01 and others added 2 commits August 19, 2026 19:15
JinjaX reserves the prop name `attrs` for its own extra-kwargs
collector and unconditionally overwrites whatever a component's
`{#def}` declares for it. docs/primitives.md documented
`:attrs="{...}"` for JinjaX usage, which compiles and runs but
silently discards the dict under a real Catalog — no error, no
passthrough. `_attrs=`/`__attrs=` is JinjaX's own escape hatch for
this collision and is what actually works; docs now show that instead.

Added tests/integration/test_jinja_attrs_passthrough.py against a real
Catalog (the unit tier's Jinja2-Environment-only tests never exercise
this: without a real Catalog, {#def} is a plain comment and `attrs`
never collides with anything). Investigating this surfaced a second,
narrower gap worth pinning down rather than leaving implicit: the
RESERVED_ATTRS collision guard itself only reliably fires when the
colliding key is *not* also one of the component's own declared prop
names. For a key that is both (`type`/`href` for Button; `name` for
the form controls), JinjaX's arg-filtering routes it straight into
that prop before render_attrs ever runs, so PrimitiveConfigError never
fires — the caller's real prop, if also passed, silently wins instead.
Documented as a JinjaX-level limitation, not a cf-ui bug to patch, and
locked in with a test so a future JinjaX version change would be
caught rather than assumed away.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ur2c6d3peuerUGgRdJ6Fvs
…l case

/review on PR #81 found two issues:

- docs/primitives.md still cited closed issues #71/#72 as pending
  follow-up for rolling attrs passthrough out past Button, contradicting
  this same PR's own new text ("name for the form controls") two
  sentences earlier — the rollout already shipped via #76/#77.
  Corrected the claim to name what actually ships today.
- The declared-prop-name collision-bypass gap was only pinned by a
  Button test, but both the docs and this test file's docstring claim
  it for "the form controls" generally. Added a FormField test
  (name= collision) so a future JinjaX change affecting a different
  prop signature would be caught, not silently assumed away.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ur2c6d3peuerUGgRdJ6Fvs
@fsecada01

Copy link
Copy Markdown
Owner Author

/review findings

2 findings, both fixed in baa48eb:

  1. [Fixed] docs/primitives.md's "Currently Button-only... (Primitives have no attribute-passthrough mechanism (Button, FormField, Select, Textarea, etc.) #71, Spike: design an attribute-passthrough mechanism for primitives #72)" line was stale — both issues are closed and Roll out attrs passthrough to Select, Textarea, FormField #76/feat(select,textarea,form-field): roll out attrs passthrough (#76) #77 already rolled attrs passthrough out to Select/Textarea/FormField, which directly contradicted this same PR's own new text ("name for the form controls") two sentences earlier. Corrected to name what actually ships today.

  2. [Fixed] The declared-prop-name collision-bypass gap (a key that's both RESERVED_ATTRS-listed and a component's own declared prop silently bypasses the PrimitiveConfigError guard) was only pinned by a Button test, but both the docs and the test module's docstring claimed it for "the form controls" generally. Added test_the_same_declared_prop_name_gap_holds_for_a_form_control (FormField's name= collision) so a future JinjaX change affecting a different prop signature would actually be caught.

All gates re-verified: pytest tests/unit (2826 passed, 13 skipped), pytest tests/integration (32 passed — 25 existing + 7 attrs tests), mkdocs build --strict clean, ruff check/ruff format --check clean.

@fsecada01 fsecada01 self-assigned this Aug 20, 2026
@fsecada01 fsecada01 added the bug Something isn't working label Aug 20, 2026
@fsecada01
fsecada01 merged commit 0c2440e into master Aug 20, 2026
7 checks passed
@fsecada01
fsecada01 deleted the fix/component-framework-ui-phase-78-jinjax-attrs-docs branch August 20, 2026 00:21
fsecada01 added a commit that referenced this pull request Aug 20, 2026
attrs passthrough on the interactive set — Button, Select, Textarea,
FormField, Icon, Badge, Box, CheckboxGroup (#70, #72, #73, #76, #77,
#82) — plus the two coverage-gap fixes found along the way (#78/#81
JinjaX docs, #79/#80 cotton integration compiler). Fully additive:
attrs defaults to {} everywhere, so every existing call site renders
byte-identically. #70 stays open for the remaining components.

Verified before tagging: 3186 passed / 13 skipped (unit), 40 passed
(integration), 236 passed / 33 skipped (E2E chromium, both js modes),
ruff check/format clean, djlint clean on both template trees (126
cotton + 105 jinja, 0 errors), mkdocs build --strict clean, and the
release workflow's own wheel guard run locally against `uv build` —
237 shipped templates and assets, all present, twine check passed on
both sdist and wheel.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant