Skip to content

feat(install widget): dependency-cooldown configurator#57

Merged
tony merged 13 commits into
mainfrom
cooldowns-ui
May 17, 2026
Merged

feat(install widget): dependency-cooldown configurator#57
tony merged 13 commits into
mainfrom
cooldowns-ui

Conversation

@tony
Copy link
Copy Markdown
Member

@tony tony commented May 17, 2026

Summary

  • Add an interactive cooldown picker to the MCP install widget so readers can apply or bypass dependency cooldowns without leaving the page. Closes [docs] Document how to bypass uv exclude-newer cooldowns when running uvx libtmux-mcp #31's docs request for a uvx-bypass path.
  • Extend the install matrix to a fourth dimension (cooldown ∈ {off, days, bypass}) — 90 server-rendered panels covering every legal (client, method, scope, cooldown) cell.
  • Inject the right per-tool flag automatically: uv's --exclude-newer P<N>D / --no-config, pip's --uploaded-prior-to P<N>D (pip 26.1+), pipx's --pip-args=--uploaded-prior-to=P<N>D. JSON/TOML config snippets gain env: UV_NO_CONFIG=1 blocks for bypass.
  • Render a per-panel italic caveat where bypass is a no-op (pip and pipx default backend have no global cooldown to skip).
  • Persist mode + days globally to localStorage (libtmux-mcp.mcp-install.cooldown.{mode,days}); the prehydrate <head> script restores both before first paint so reloading never flashes through the default.
  • Fix Sphinx 8's copy_asset_file no-overwrite behavior in _assets.py so widget JS/CSS actually re-ship on incremental builds (a quiet trap — the build reported success while serving stale assets).

Changes by area

Data model & rendering

  • docs/_ext/widgets/mcp_install.py — New Cooldown dataclass + COOLDOWNS tuple. Panel gains cooldown, pip_prereq, note fields. _body_for(client, method, scope, cooldown) returns (body, language, note). <DAYS> sentinel marks the spot in days-mode bodies where the user-configurable day count slots in.
  • docs/_ext/widgets/_base.py — New cooldown_days_slot Jinja filter swaps the post-Pygments &lt;DAYS&gt; for <span data-cooldown-days-slot>7</span> inside the highlighted string literal. The number inherits Pygments colors and stays selectable for copy-paste.
  • docs/_ext/widgets/_prehydrate.py — Reads cooldown.mode / cooldown.days from localStorage in the inline <head> script. The @layer mcp-install-prehydrate rules enumerate every legal (client × method × scope × cooldown) quadruple so first paint always lands on the correct cell.
  • docs/_ext/widgets/_assets.py — Replace copy_asset_file with shutil.copy2 to force overwrites on incremental builds (Sphinx 8 emits misc.copy_overwrite warnings and aborts otherwise).

Template & UX

  • docs/_widgets/mcp-install/widget.html — Adds the right-edge cooldown control (checkbox + label button + ? help) on the method-tab row, plus a parallel settings sub-view with radio mode picker, days input, and a collapsible What are cooldowns? explainer linking cooldowns.dev and the Datadog Security Labs writeup.
  • docs/_widgets/mcp-install/widget.css — Styles the cooldown control, settings view, days input, and the per-tool cooldown caveat row. CSS-only view swap via html[data-mcp-install-view="settings"] (no transitions — see commit f23d7d5 for why).
  • docs/_widgets/mcp-install/widget.jssetCooldownMode() / setCooldownDays() / setView() plus delegated handlers for the checkbox, radio, days input, back link, and help button. Auto-save on every change (matching the hsk-django flashcard settings pattern); input events update slot textContent in real time while change events persist + broadcast.

Tests & changelog

  • tests/docs/test_widgets.py — 4-axis cross-product asserts; per-tool body-shape tests for each cooldown mode; verifies the pip/pipx bypass caveat note; confirms Panel.pip_prereq is set only for the pip method and carries the cooldown flag when applicable.
  • CHANGES — User-facing entry under ### What's new per AGENTS.md's deliverable test.

Design decisions

Days slot via post-Pygments span injection. Server-renders the snippet as …P<DAYS>D…. Pygments highlights the surrounding text, then the cooldown_days_slot filter swaps &lt;DAYS&gt; for a <span data-cooldown-days-slot>7</span>. The span lives inside the Pygments string-literal span so the number inherits its color; widget.js updates textContent on every input keystroke. Copy-paste captures the visible number. The alternative — CSS ::before { content: var(--days) } — would have made the value uncopyable.

View toggle is transient, mode is persistent. Cooldown mode and days persist across pages and reloads. The settings-vs-install view does not — a user mid-form who reloads expects to see the snippet on next load, not the open form. Implementing view as html[data-mcp-install-view] (set only by JS, never restored by prehydrate) gets this for free.

Bypass for non-uv tools is shown, not hidden. Pip and pipx default backend have no global cooldown to bypass, so their bypass panels render the same body as off but surface an italic per-tool note. This is more honest than silently making the radio inert on certain method tabs, and the note links the user to the uv-backed fallback (pipx[uv]).

Single source of truth for defaults. DEFAULT_COOLDOWN_MODE / DEFAULT_COOLDOWN_DAYS live in Python and are serialized into the inline prehydrate script. Adding a new cooldown mode or moving the default later only requires editing the Python module — the script extends automatically.

Verification

Every legal (client, method, scope, cooldown) quadruple has a matching CSS rule in the prehydrate <style> block. With the front page built, this should match 90:

$ grep -oc 'data-mcp-install-cooldown-mode="[^"]*"\] \.lm-mcp-install__panel' docs/_build/html/index.html

The days slot span appears exactly once per days-mode panel (30 across the front page):

$ grep -oc 'data-cooldown-days-slot' docs/_build/html/index.html

The bypass caveat note renders for the 20 panels where bypass is a no-op (pip + pipx, every scope row):

$ grep -oc 'lm-mcp-install__cooldown-note' docs/_build/html/index.html

Test plan

  • uv run pytest --reruns 0 — widget unit tests cover 4-axis cross product, per-tool flag injection, pip/pipx bypass caveat notes, and Panel.pip_prereq being method-scoped.
  • uv run ruff check . + uv run ruff format --check .
  • just mypy — strict-typed widget framework passes.
  • just build-docs — all dirhtml targets build; the pre-existing duplicate-ID warnings in docs/tools/**/*.md are unrelated.
  • Fresh load: empty localStorage → front page shows the existing default snippet, cooldown checkbox unchecked, install view active.
  • Toggle on: click the checkbox → settings view opens, mode flips to days with the default 7, snippet shows --exclude-newer P7D.
  • Real-time days update: type 30 in the days input → all visible snippets update to P30D before blur (input event hook).
  • Bypass mode: pick Bypass any global cooldown → snippet swaps to uvx --no-config libtmux-mcp (CLI) or env: { "UV_NO_CONFIG": "1" } (JSON config).
  • Per-tool caveat: switch to the pip method while in bypass mode → small italic note appears below the snippet explaining pip has no global cooldown.
  • Reload persistence: configure bypass + days=14 → reload → first paint shows the bypass panel and the days input pre-filled to 14 with no visible flicker (prehydrate-driven).
  • SPA navigation: configure on /, navigate to /clients/ → cooldown state survives the gp-sphinx soft-nav.
  • Uncheck: clicking the checkbox while a non-off mode is active resets mode to off and the snippets revert to their unflagged forms.

tony added 4 commits May 17, 2026 09:14
Add a fourth dimension to the install picker's (client, method, scope)
matrix: cooldown mode in {off, days, bypass}. Off keeps the existing
command intact; days inserts the tool-appropriate cooldown flag with a
``<DAYS>`` sentinel for the user-configurable day count; bypass inserts
``--no-config`` (uvx) or an ``env: UV_NO_CONFIG=1`` block (JSON/TOML).

Per-tool flag forms — verified against pip 26.1+ source and Astral's
``--exclude-newer`` docs:

* uvx days   →  ``uvx --exclude-newer P<N>D libtmux-mcp``
* uvx bypass →  ``uvx --no-config libtmux-mcp``
* pipx days  →  ``pipx run --pip-args=--uploaded-prior-to=P<N>D ...``
* pip days   →  cooldown applies to the prereq ``pip install`` line via
                ``--uploaded-prior-to P<N>D`` (pip ≥ 26.1)

Pip and pipx have no global cooldown to bypass — their bypass panels
emit the same body as off plus a per-panel ``note`` row explaining the
caveat. Codex's project scope keeps its TOML body and gains the
cooldown flag inside the ``args`` array (or ``env`` block for bypass).

Server-renders 90 panels (10 scope rows x 3 methods x 3 cooldown
modes). A new ``cooldown_days_slot`` Jinja filter runs after Pygments
and swaps the escaped ``&lt;DAYS&gt;`` sentinel for a
``<span data-cooldown-days-slot>7</span>`` whose textContent
``widget.js`` will update in phase 2 — but the default 7 already ships
inline so first paint shows the correct snippet without any post-paint
DOM mutation.

The prehydrate ``@layer`` rules now enumerate every legal
``(client, method, scope, cooldown-mode)`` quadruple (90 panel-active
selectors, all ``!important`` so the CSS Cascade Level 5 layer-priority
reversal continues to beat unlayered preflight rules). The inline
``<head>`` script gains two reads (``cooldown.mode`` / ``cooldown.days``)
and emits both as ``data-mcp-install-cooldown-*`` attrs on ``<html>``
before first paint.

No UI control yet — cooldown mode hard-defaults to ``off`` for every
visitor in this phase, so the front page reads identically to before.
The settings panel and the right-edge checkbox land in the next commit.
…view

Wire up the interactive layer for the cooldown picker landed in the
previous commit. A new ``Configure cooldowns`` checkbox sits at the
right edge of the install-method tab row; clicking the checkbox flips
mode between ``off`` and ``days`` (defaulting days to 7) and opens the
settings sub-view, while clicking the ``Configure cooldowns`` label
always opens settings regardless of state. A ``?`` button opens the
collapsible explainer with links to cooldowns.dev and Datadog's
dependency-cooldown writeup.

The widget body now has two parallel sub-views — install (the existing
30 cooldown-aware panels) and settings (radio mode + days input +
expandable help). CSS swaps them via ``html[data-mcp-install-view]``.
The settings form auto-saves on every change (matching the hsk-django
flashcard pattern) — there's no Save button. ``← Back to installer``
returns to the install view without losing settings.

Days slot updates fire on every ``input`` event so the snippet changes
in real time as the user types; persistence and broadcast fire on
``change`` only so localStorage writes don't hammer per-keystroke.

``_assets.py`` switches from ``copy_asset_file`` to ``shutil.copy2``:
recent Sphinx releases tightened the helper to refuse overwriting an
existing destination (emitting ``misc.copy_overwrite`` warnings),
which left widget JS/CSS stale on every incremental rebuild. The
cache-busting ``?v=<hash>`` already keeps browsers honest.

Playwright-verified flows:

* fresh visit (off) -> click checkbox -> settings opens, mode=days,
  default 7-day cooldown applied to every snippet
* edit days input to 30 -> snippet shows ``--exclude-newer P30D`` live
* radio to bypass -> snippet swaps to ``--no-config`` form (or ``env``
  block for JSON-kind clients); pip/pipx surface the per-tool
  no-op note
* reload -> prehydrate restores mode + days before first paint, no
  visible flicker; view resets to install (transient by design)
User-facing one-paragraph entry under the existing ``### What's new``
heading, per AGENTS.md "deliverable test": describes what the reader
can now do (pick a cooldown delay or bypass a global cooldown) and
points at cooldowns.dev / Datadog for context. Closes #31.
mypy on full-tree (CI runs ``uv run mypy .``) flagged the ``in``
operator usage on ``Panel.pip_prereq`` because the ``all(... is not
None)`` guard above doesn't carry narrowing into the per-instance
checks below. Add explicit ``is not None`` asserts on the two specific
panels before the membership tests. Functionally a no-op — the prior
``all`` already proves it.
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 17, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.02%. Comparing base (bdbbcad) to head (e20a50b).

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #57      +/-   ##
==========================================
+ Coverage   85.39%   86.02%   +0.62%     
==========================================
  Files          40       40              
  Lines        2349     2454     +105     
  Branches      300      325      +25     
==========================================
+ Hits         2006     2111     +105     
  Misses        260      260              
  Partials       83       83              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

tony added 7 commits May 17, 2026 09:39
… button

Pull the ``← Back`` link and the *Dependency cooldowns* title onto a
single row at the top of the settings sub-view, flush to the body's
content area. Reshape the back link as a compact bordered button
(``font-size: 0.85em``) so it reads as an action instead of inline
prose; the title drops from ``1.1em`` to ``1em`` so it inherits the
widget's body type scale instead of sticking out.

WAI-ARIA polish:

* visible label shrinks to ``← Back`` (compact), but the ``<button>``
  carries ``aria-label="Back to installer"`` so screen readers still
  get the destination
* the decorative left arrow gets ``aria-hidden="true"`` to keep it
  out of the accessible name
* the surrounding ``role="dialog"`` keeps its ``aria-label``, and the
  visible ``<h3>`` provides the heading semantic next to it
…ISO durations

End-to-end testing with the documented snippets surfaced a runtime
failure on pipx: ``--pip-args=--uploaded-prior-to=P7D`` is rejected
inside pipx 1.8.0's bundled venv because the bundled pip is older than
26.1 and only ISO 8601 duration syntax was added there. Pip 26.0
(2026-01) accepted absolute datetimes only, and that's what ships in
the current pipx release.

Switch every cooldown days-mode snippet to the absolute-date form
``YYYY-MM-DD`` computed from ``today (UTC) - savedDays``. The form is
accepted across uv's ``--exclude-newer``, pip 26.0+'s
``--uploaded-prior-to``, and pipx's bundled pip — portable across the
full matrix. ``widget.js`` adds a ``daysToIsoDate(n)`` helper that
recomputes the cutoff on every days-input change so the rendered
snippet matches what the user would run *that day*.

Rename ``<DAYS>`` -> ``<COOLDOWN_DATE>`` sentinel; the Jinja filter
swaps it for a ``data-cooldown-date-slot`` ``<span>`` whose default is
the server-rendered ``today - DEFAULT_COOLDOWN_DAYS`` ISO date.
Build-time staleness is bounded by build cadence — JS refreshes on
first hydration so any visitor sees a current cutoff.

Verified live:

* ``uvx --exclude-newer 2026-05-10 libtmux-mcp`` runs cleanly
* ``pipx run --pip-args=--uploaded-prior-to=2026-05-10 libtmux-mcp``
  runs cleanly (this was the broken cell)
* ``pip install --dry-run --uploaded-prior-to 2026-05-10 libtmux-mcp``
  resolves libtmux-mcp 0.1.0a6 (the only release older than the cutoff)
…pip to duration form

Replace the single ``cooldown.mode`` enum with three orthogonal
localStorage keys: ``cooldown.enabled`` (master on/off), ``cooldown.type``
(``"days"`` vs ``"bypass"``), and ``cooldown.days`` (day count). The
*Configure cooldowns* checkbox now flips ``enabled`` only — it no
longer doubles as the entry point into the settings view. The
*Configure cooldowns* label / ``?`` button remain the explicit settings
entry. Touching the radio or days input auto-sets ``enabled=1`` so a
user who configures clearly wants the cooldown on.

Switch uvx and pip days-mode snippets from absolute date to ISO 8601
duration ``P<N>D``. uv stores ``--exclude-newer P7D`` as
``ExcludeNewerValue::Relative(ExcludeNewerSpan)`` and recomputes the
cutoff against ``current_time()`` on every resolver call (see
``crates/uv-distribution-types/src/exclude_newer.rs:64-89``); pip 26.1+
resolves the duration at flag-parse time per invocation. Both keep
the user's saved ``.mcp.json`` arg fresh — every spawn evaluates
``now - N days`` against that spawn's clock, not against the day the
config was written.

pipx days bodies keep the absolute-date form because pipx 1.8.0
bundles a pip older than 26.1 that rejects ``P<N>D`` with
``Invalid isoformat``. JS still recomputes the date on every page
load so the rendered snippet stays current.

Two sentinels coexist in the panel bodies — ``<COOLDOWN_DURATION>`` for
uvx + pip, ``<COOLDOWN_DATE>`` for pipx — and the
``cooldown_days_slot`` Jinja filter swaps both post-Pygments.
``widget.js`` updates both slot kinds (``[data-cooldown-duration-slot]``
and ``[data-cooldown-date-slot]``) on every days-input change.

The 90-rule ``@layer mcp-install-prehydrate`` panel-active selectors
rewrite to key on the ``(enabled, type)`` pair: 30 rules for
``enabled=0`` → ``[data-cooldown="off"]``, plus 30 each for the days
and bypass type when enabled=1. Same rule count as before, different
attribute shape.

Smoke-tested live:

* ``uvx --exclude-newer P7D libtmux-mcp`` runs cleanly
* ``pipx run --pip-args=--uploaded-prior-to=2026-05-10 libtmux-mcp``
  runs cleanly (the cell that motivated the absolute-date fallback)
* ``pip install --dry-run --uploaded-prior-to P7D libtmux-mcp`` resolves
  the right older release on host pip 26.1.1

Full UX trace via Playwright: checkbox toggles enabled with view
unchanged, label opens settings, radio/days input auto-enable, back
returns to install view, uncheck returns to off variant.
``uv run ruff format --check .`` flagged three files reformatted on
the prior commit. Apply the formatter so CI passes.
Clicking any client / method / scope tab while the cooldown settings
view is open now returns to the install view. A tab click is an
install-side action — if the reader was mid-configuring cooldowns,
the click means "I want to see the snippet for this new selection",
so the settings drawer dismisses to surface the updated panel.

Verified end-to-end: open settings, click Cursor / pipx / scope tab,
view flips to ``install`` each time with the new selection reflected.
The ``_json_body`` helper composed the snippet via ``textwrap.dedent``
on an f-string with a multi-line ``inner`` substitution. The first
member (``"command"``) inherited the template's 20-space source-indent,
but continuation members (``"args"``, ``"env"``) only carried the
12-space ``server_indent`` prefix. ``dedent`` then computed the common
leading whitespace across all lines — limited by the lower-indent
continuation lines — and stripped that uniformly, leaving ``"command"``
at 12 spaces and ``"args"`` at 4. The rendered Cursor and Claude
Desktop snippets came out visibly broken.

Drop ``textwrap.dedent`` entirely and build the JSON string with
explicit per-line indents. Each server-object member now carries its
own 12-space prefix at build time, joined with ``,\n``, and the
surrounding ``mcpServers`` / ``tmux`` lines are spelled out in the
return expression. The output is byte-for-byte stable regardless of
source whitespace, and the unused ``textwrap`` import drops out.

The ``_toml_body`` helper already used flat ``"\n".join`` and was
unaffected.
…o kill load flicker

Native ``<input type="checkbox">`` doesn't react to CSS attribute
selectors — its rendered glyph follows the ``.checked`` property,
which only ``widget.js`` can set on DOMContentLoaded. When a user
saved ``cooldown.enabled = "1"`` and reloaded, the SSR HTML had an
unchecked input, paint showed native-unchecked, JS then flipped
``.checked = true``, producing a visible one-frame unchecked → checked
flicker on every load.

Apply the same prehydrate pattern that already drives panel and tab
state: strip the native chrome with ``appearance: none`` inside the
``@layer mcp-install-prehydrate`` block, and re-render the checkbox
visual from CSS keyed off
``html[data-mcp-install-cooldown-enabled="1"]``. The inline ``<head>``
script already sets that attribute from localStorage before first
paint, so the visual is correct at first paint without waiting for
JS.

The check mark is a centred ``✓`` ``::after`` pseudo on a brand-blue
background — legible in both light and dark modes since the brand
colour stays blue across themes. ``appearance: none`` would have
also dropped the focus outline; the rule re-adds a brand-coloured
``:focus-visible`` ring so keyboard navigation accessibility stays
intact.

``widget.js``'s existing ``applyCooldownToWidget`` continues to sync
``.checked`` post-DOMContentLoaded — needed for screen readers, the
``onChange`` click handler's read of ``el.checked``, and form-submit
semantics. That sync is invisible because the CSS visual is already
correct.

Verified live with ``localStorage.cooldown.enabled = "1"`` + reload:
``getComputedStyle(checkbox).backgroundColor`` reads ``rgb(10, 75, 255)``
(brand-primary) at first paint, no flicker visible. Empty-localStorage
reload renders unchecked. Toggle on/off via click updates the visual
synchronously with the ``<html>`` attribute change.
@tony
Copy link
Copy Markdown
Member Author

tony commented May 17, 2026

Code review

Found 3 issues:

  1. widget.html top docstring claims the cooldown_days_slot Jinja filter swaps the sentinel for a <span data-cooldown-days-slot>, but the slot was split into data-cooldown-duration-slot (uvx + pip) and data-cooldown-date-slot (pipx) in a later commit on the same branch. The comment's attribute name doesn't exist in the rendered HTML.

PygmentsBridge — so the output is byte-identical to a native
``.. code-block::`` block, meaning sphinx-copybutton + its prompt-strip
regex work automatically. The output is then run through
``cooldown_days_slot`` which swaps the post-Pygments ``&lt;DAYS&gt;``
sentinel for a ``<span data-cooldown-days-slot>`` whose textContent
``widget.js`` updates on every cooldown-days input change.
#}
<div class="lm-mcp-install lm-mcp-install--{{ variant }}">

  1. widget.css cooldown-days slot comment calls data-cooldown-days-slot the JS hook for updating textContent on settings save. The JS uses [data-cooldown-duration-slot] and [data-cooldown-date-slot] separately (see widget.js lines around 463 / 466) — data-cooldown-days-slot is not present anywhere in the codebase.

}
/* Cooldown days slot inside snippets: inherits Pygments color from
* the surrounding string-literal span. ``data-cooldown-days-slot``
* is the JS hook for updating textContent on settings save. */
.lm-mcp-install__cooldown-days {
/* No own styling — the wrapping Pygments span carries the color. */

  1. mcp_install.py module docstring and the _DURATION_SENTINEL / _DATE_SENTINEL comment block both name the filter cooldown_slots, but the registered Jinja filter (per _base.py#L108) is cooldown_days_slot.

flag stays fresh forever once saved in an MCP config. ``<COOLDOWN_DATE>``
lands in pipx bodies because pipx 1.8.0 bundles a pip older than 26.1
that rejects the duration form; JS recomputes the absolute date on
every page load. Both sentinels are swapped by ``cooldown_slots``
in ``docs/_ext/widgets/_base.py``.
"""

DEFAULT_COOLDOWN_DAYS: int = 7
# Two sentinels swapped by ``cooldown_slots`` in ``_base.py`` after
# Pygments has escaped them to ``&lt;...&gt;``.
#
# * ``<COOLDOWN_DURATION>`` is used by uvx and pip days bodies. uv stores

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

tony added 2 commits May 17, 2026 11:42
…trs + filter

Three comments / docstrings in this branch's earlier commits still
named the cooldown slot by its pre-split attribute
(``data-cooldown-days-slot``) and the Jinja filter by an incorrect
shortened form (``cooldown_slots``). The slot was later split into
``data-cooldown-duration-slot`` (uvx + pip days bodies) and
``data-cooldown-date-slot`` (pipx days bodies); the filter is
registered in ``_base.py`` as ``cooldown_days_slot``. A grep against
the comment names returned no live code, so any reader following
them would search for symbols that don't exist.

* ``widget.html`` top docstring — rewrite the two sentences that
  describe the filter: name both sentinels (``<COOLDOWN_DURATION>``,
  ``<COOLDOWN_DATE>``) and both slot spans, and cite the filter's
  factory location for grep-ability.
* ``widget.css`` cooldown-days slot comment — name both slot
  attributes and what each carries.
* ``mcp_install.py`` module docstring + sentinel comment — fix the
  two ``cooldown_slots`` references to ``cooldown_days_slot``.

Functional code unchanged. Closes the only finding from the automated
code review on this PR.
…ilter

The Jinja filter that swaps post-Pygments ``&lt;COOLDOWN_DURATION&gt;``
and ``&lt;COOLDOWN_DATE&gt;`` sentinels for the corresponding slot
``<span>`` elements had no direct test coverage. Existing tests
verified body-string composition (the input side) and the rendered
HTML's panel structure (via the full-build path), but nothing
asserted that the filter itself produces the expected spans. A
silent regression — for example, dropping one of the two
``str.replace`` calls in ``make_cooldown_days_slot_filter`` — would
have shipped raw sentinel text into every days-mode snippet and gone
undetected.

Add six tests:

- **Duration sentinel swap**: input containing ``&lt;COOLDOWN_DURATION&gt;``
  emits a ``data-cooldown-duration-slot`` span carrying the
  ``P<DEFAULT_COOLDOWN_DAYS>D`` default text.
- **Date sentinel swap**: input containing ``&lt;COOLDOWN_DATE&gt;``
  emits a ``data-cooldown-date-slot`` span carrying an ISO date.
- **No-op without sentinels**: off and bypass bodies, which carry no
  sentinel, pass through unchanged.
- **Both sentinels in one HTML**: a single string containing both
  sentinels gets both spans — defense against the swap order
  accidentally clobbering one form.
- **Markup return type**: the output is ``markupsafe.Markup`` so
  Jinja autoescape doesn't re-escape the injected span.
- **End-to-end wiring**: a built page renders both
  ``data-cooldown-duration-slot`` and ``data-cooldown-date-slot``
  spans and contains no raw escaped sentinels — guards against
  regressing the ``jenv.filters["cooldown_days_slot"]`` line in
  ``BaseWidget.render``.

Closes the test gap surfaced by the automated code review on PR #57.
@tony tony merged commit a739995 into main May 17, 2026
9 checks passed
@tony tony deleted the cooldowns-ui branch May 17, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[docs] Document how to bypass uv exclude-newer cooldowns when running uvx libtmux-mcp

2 participants