feat(themes): DaisyUI theme + django-cotton theme dispatch (#6) - #9
Conversation
Adds the full 14-component DaisyUI set to both template sets and the
mechanism django-cotton needed to switch themes at all.
The two engines dispatch differently, deliberately. JinjaX already
switched by directory. django-cotton could not: <c-cf.card> resolves
through a single global COTTON_DIR, which cf-ui must not touch (0.1.1
reverted exactly that, because it broke consumers whose own components
live at cotton/<their-app>/*.html). So the public component stays at the
fixed path cotton/cf/<name>.html and becomes a wrapper that declares
<c-vars> and includes cotton/_themes/<theme>/<name>.html. Consumer
templates are untouched — the E2E tier proves it by rendering the Bulma
gallery's own templates under CF_UI_THEME="daisy".
Tailwind tree-shaking is the failure mode this theme actually risks, and
it fails silently. Both causes are tested: cf_ui.themes publishes the
absolute content globs (docs are checked against the same constant), and
every variant class is written out in full rather than assembled as
alert-{{ type }}, which the scanner cannot see.
Alpine is unchanged — cfModal, cfNavbar, cfPanel, cfTabs and the $cf
store are theme-independent; only the toggled class differs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NhqNRBg83czKfr8L6FF5xf
The pinned ruff-pre-commit hook (v0.3.0) and the ruff CI resolves from `dev` (0.16.x) infer first-party differently for a src/ layout, so each reverted the other's import fix: prek went green and CI then failed I001 on the file the hook had just "fixed". Stating known-first-party removes the ambiguity for both versions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhqNRBg83czKfr8L6FF5xf
Two defects found by adversarial review of the PR diff, both of which the original tests were too weak to catch. tailwind_content_globs() emitted native Windows paths. Those strings are pasted into a tailwind.config.js or an @source directive, where a backslash is a JavaScript escape and fast-glob reads it as an escape rather than a separator — so the glob matches nothing and every class is shaken out, which is the exact silent failure the API exists to prevent. The old test only asserted Path.is_absolute(), which backslashes satisfy. The navbar toggled `flex` onto an element that kept `hidden`. Both are display utilities in the same layer and Tailwind emits `hidden` last, so the class list changed and the menu still never appeared. The E2E test asserted classList membership, so it passed a broken component; it now asserts computed display, verified to fail against the old markup. The DaisyUI E2E gallery now loads the Tailwind play CDN. DaisyUI ships component classes but no utilities, so without it `hidden` and `lg:flex` resolved to nothing and this tier could not observe layout behavior at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhqNRBg83czKfr8L6FF5xf
Adversarial review — PR #9Run against the PR diff (74 files, +2203/−333). Findings were produced by executing probes against the built code, not by reading it. Two real defects found and fixed in 1.
|
Closes #6.
Implements the DaisyUI theme in both template sets, plus the dispatch mechanism django-cotton needed before a theme switch was possible at all.
Acceptance criteria
js_onandjs_off).jinja/daisy/*.jinja(14) andcotton/_themes/daisy/*.html(14). 393 unit+integration, 42 E2E (2 skipped — the existingjs_off-only guards).CF_UI_THEME = "daisy"on Django,theme="daisy"on FastAPI/Litestar. The DaisyUI E2E server renders the same consumer templates as the Bulma one —tests/integration/cotton_app/templates/is not duplicated or edited — so this is executed, not asserted.docs/daisyui.md+cf_ui.themes.tailwind_content_globs();tests/unit/test_tailwind_content.pycovers both ways tree-shaking actually happens.cfModal,cfNavbar,cfPanel,cfTabs) work unchanged — theme-independent and should stay that way.cf_ui_alpine.jsis untouched. Only the toggled class differs (is-active→modal-open/tab-active), and E2E exercises open/close, dismiss, expand, burger and tab activation under DaisyUI.The one design decision worth reviewing
The issue says to fill
templates/cotton/daisy/. That directory layout no longer exists — 0.1.1 removed per-theme cotton directories andCF_UI_THEMEdid not select a cotton path at all, so the cotton set had no way to switch themes. I raised this before implementing; the chosen approach was partial dispatch:Theme markup moves to
cotton/_themes/<theme>/<name>.html.{% include %}inherits the caller's context, so props and{{ slot }}reach the partial without redeclaring<c-vars>— it stays in exactly one place per component.Why not the obvious alternative of setting
COTTON_DIRper theme: that is precisely the 0.1.1 regression (#4).COTTON_DIRis a single global prefix for every<c-foo.bar>lookup, so setting it breaks any consumer whose own components live atcotton/<their-app>/*.html.tests/unit/test_consumer_compatibility.pystill passes unchanged.Consequence worth a second opinion: each wrapper now does
{% load cf_ui %}, so"libraries": {"cf_ui": "cf_ui.templatetags.cf_ui"}is required for components to render, not just for the asset tags. It was already documented as required (thecf_ui.djangoapp label defeats templatetag autodiscovery), but the failure mode for anyone who skipped it moves from "asset tags missing" to "nothing renders." Called out explicitly indocs/daisyui.mdand the README.Tailwind tree-shaking
This is the failure mode the issue flagged, and it is silent — correct markup, no styling, no error. Two distinct causes, both tested:
tailwind_content_globs()returns absolute globs derived fromcf_ui.__file__, so they work from an editable or site-packages install;python -m cf_ui.themesprints them. The docs are checked against the same constant, so they cannot drift.alert-{{ type }}is invisible to it. Every variant class is written out in full instead, and a test scans the DaisyUI templates for class tokens split across a template construct. The detector has a meta-test, since a guard that cannot fail is not a guard.The prop vocabulary is deliberately unchanged across themes:
type="danger"still works; DaisyUI has noalert-danger, so the templates map it ontoalert-errorinternally.Not in the issue, but blocking
pyproject.tomlgained[tool.ruff.lint.isort] known-first-party = ["cf_ui"](separate commit). The pinnedruff-pre-commitv0.3.0 and the ruff CI resolves fromdev(0.16.x) classifycf_uidifferently under asrc/layout and each reverted the other's import fix —prekgreen, CI red on the file the hook had just fixed. Stating it explicitly makes both agree. This bit issue #5 too.Follow-ups not folded in
cotton/{bootstrap,daisy,fomantic,foundation}andjinja/{bootstrap,fomantic,foundation}still holdPLANNED.mdstubs; the daisy ones are now gone. Migrating the remaining three to_themes/is their own tickets.--spacingaliased for all themes, p3 outside the contrast gate) are untouched here.Verification