Commit Graph
25 Commits
Author SHA1 Message Date
dsql 438a15813f release: 1.0.0
first stable release. pre-1.0.0 verification complete: all surviving MED regressions and
gaps resolved and independently re-fired, tree audited clean across the suite.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-09 18:53:15 -04:00
dsql b8d7bd728a docs: pin README install to v0.1.8, the newest existing tag
README pinned @v0.1.9 but that tag was never cut (newest is v0.1.8), so the documented
install line fails to resolve - point it at v0.1.8. pyproject stays at 0.1.9 under the
version freeze; the tag is cut at the 1.0.0 decision.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-06 19:38:49 -04:00
dsql d925112752 fix: skip a None content value in a multi-entry page join instead of rendering literal 'None'
the multi-entry content-join f-strung a dict entry's content with no None guard, so a
{'content': None} entry following a text entry produced a literal 'None' line
('hello' + None -> 'hello\nNone'). a None content now contributes nothing to the join.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-06 16:41:19 -04:00
dsql 9b13674440 docs: fix broken antecedent and de-duplicate the cache-button omit note
The omit-cache sentence was spliced mid-paragraph between the raw-id problem
statement and "The cache button fixes that", so "that" resolved to the wrong
antecedent (the omit-cache mechanics instead of the raw-id rendering
problem). The same note already exists standalone later in the section, so
the spliced copy was also a duplicate. Drop it from the lead paragraph and
keep the one standalone copy.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-06 00:12:11 -04:00
dsql 9e55b29e11 fix: multi-entry pages join dict content instead of dropping all but the last
The dict-entry catch-all assigned kwargs["content"] = value directly, so a
per_page > 1 page combining multiple dict entries silently kept only the
last entry's content while embeds/files/buttons from every entry still
accumulated - an asymmetry that dropped text with no error. The dict
branch's content key now routes through the same None-check/newline-join
accumulation the plain-str branch already used, so every entry's content
survives in order.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 19:07:24 -04:00
dsql a2cb616667 refactor: derive __version__ from package metadata (single source)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 16:59:33 -04:00
dsql bdf06b9777 docs: add missing cache_sleep to constructor options list
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 16:23:41 -04:00
dsql 4e4cd0687b docs: compress prose/module docstrings, em-dash->hyphen (de-bloat wave 1)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 00:13:45 -04:00
dsql 8addecd64f fix: cache button renders on single page; cache_button edit swallows deleted/expired message (v0.1.7)
update_buttons gated the cache button behind the nav row, so a single-page
paginator with a non-empty cache never showed it despite the README promising
otherwise; the cache button now renders whenever self.cache is truthy,
independent of nav, and start() keeps the view alive for that case too.

cache_button's post-sleep message.edit was unguarded, so a message deleted or
expired during cache_sleep raised out of the callback; it now mirrors
on_timeout's NotFound/Forbidden swallow and HTTPException log.

also compresses the module and method docstrings (no behavior change).

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-02 23:23:26 -04:00
dsql bba01fa931 refactor: rename ButtonPaginator -> DPYPaginator (keep ButtonPaginator as alias)
the paginator class is now DPYPaginator, matching the package name (dpy_paginator),
for naming consistency with the suite. ButtonPaginator is kept as a back-compat alias
(ButtonPaginator = DPYPaginator), so existing imports and call sites keep working
unchanged. both names are exported in __all__. mirrors the suite's alias convention
(aioweb Response/aiowebResponse, envelope_crypto EnvelopeCrypto/PCICrypto).

verified against discord.py 2.7.1: both names resolve to the same class, construction
via either name works, the empty-pages ValueError message updated. v0.1.6.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-02 17:50:24 -04:00
dsql 546b9080ef fix: rebuild fresh File per render, convert Attachments in files= list, thread page state through locals
discord.py closes a discord.File's fp after every send/edit, so reusing the
same File object across renders raised ValueError on navigating back to a
file page; reset_files now rebuilds a fresh File from the same source
instead. The dict page 'files' list never converted discord.Attachment to
File (unlike the singular 'file' key), crashing reset_files with
AttributeError; it now converts via to_file() like its sibling. get_page_kwargs
also built render state in instance attributes across real await points,
letting two near-simultaneous interactions interleave into a merged render;
it now builds in locals and threads them through the recursive branch,
publishing self.current_page_buttons only once a page is fully assembled.

Bump to v0.1.5.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-02 17:30:26 -04:00
dsql 66933b1e2e chore: ignore .claude/ dir (CLAUDE.md now lives under .claude/)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 21:55:13 -04:00
dsql 18bbde19b6 docs: note cache=None/[] disables the cache button
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 21:41:37 -04:00
dsql fa5cdf3e1d fix: F3 guard per_page>=1; swallow on_timeout HTTPException
per_page<=0 raised ZeroDivisionError (==0) or yielded a negative max_pages (<0) at the
divmod; now a clear ValueError. on_timeout swallows a transient delete HTTPException (with
a log) so a best-effort cleanup doesn't surface as an unretrieved-task traceback. add the
module logger.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 21:35:01 -04:00
dsql 1416375e40 fix: cache=[] disables the button (not raises); nav row stays suppressed on re-render (v0.1.4)
M-3: the cache-length guard used 'cache is not None', so cache=[] ([] is not None) hit the
length check and raised at construction — contradicting README/CLAUDE that say a falsy
cache disables the button (matching the render path 'if self.cache:'). guard now uses
'if cache' so [] and None both disable; a non-empty too-short cache still raises.

dpy_paginator-F2: update_buttons() now ANDs nav with max_pages>=2, so a single-page
re-render (update_page -> _build_render_kwargs, default nav=True) no longer resurrects the
nav row over the page's custom buttons. start()'s single-page branch simplified accordingly.

verified by execution with real discord.py: []-disables and None-disables both construct,
real cache renders, too-short still raises; single-page re-render keeps nav suppressed +
custom button, multi-page control still shows nav.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 20:47:43 -04:00
dsql de572af675 docs: pin install line to release, note unpinned-latest option
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 18:13:51 -04:00
dsql 234f663f04 docs: show unpinned install line; note tag-pinning for reproducibility
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 18:07:36 -04:00
dsql 8c3bacb2f2 fix: reject wrong-typed embed/file page keys; author_id is-None check (v0.1.3)
- a dict page with a wrong-typed embed/embeds/file/files key now raises a clear
  paginator-side ValueError instead of forwarding it to discord.py as a conflicting
  kwarg (opaque TypeError) (L12)
- interaction_check uses 'author_id is None' so an author_id of 0 still restricts (nit).

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 17:58:09 -04:00
dsql 20ea4f9291 fix: single page keeps custom buttons instead of dropping the view (v0.1.2)
a single-page result (max_pages < 2) suppressed the navigation row by dropping the
whole view, which also discarded the consumer's custom per-page buttons. now: if the
page carries custom buttons, keep the view and rebuild with update_buttons(nav=False)
— nav items suppressed, custom buttons kept, and stop() NOT called so their callbacks
still fire. a page with no custom buttons keeps the original drop-the-view behavior.

verified by execution against real discord.py: single page + custom button -> start()
-> callback FIRES on click (view kept, stop() not called); negative control on the old
code drops the button entirely; the no-button single-page case is unregressed.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 17:26:04 -04:00
dsql bca6b874c6 fix: only pass ephemeral on interaction paths, not raw-channel send
start() put ephemeral into send_kwargs unconditionally, so the raw Messageable.send() path raised TypeError (discord.py's channel send has no ephemeral param). ephemeral is now passed only on the interaction-response paths (followup.send / response.send_message), which support it; raw-channel send never receives it.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-29 01:10:25 -04:00
dsql f6caa6f016 fix: wire on_timeout so delete_message_after actually deletes
delete_message_after was stored but never read and no on_timeout override existed, so the documented 'delete the message on timeout' never happened. added on_timeout that deletes self.message when the flag is set, swallowing only the expected discord.NotFound (already deleted) / discord.Forbidden (no permission) and letting any unexpected error surface.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-28 18:45:25 -04:00
dsql 6dae35001e fix: out-of-range page wrap returns a per_page slice; validate cache length
get_page() out-of-range/negative wrap reset to page 0 but returned pages[0] (a single item) even with per_page>1, mis-shaping the page downstream; it now reroutes through get_page(0) so the normal slice logic applies. cache shorter than max_pages now raises ValueError at construction instead of an IndexError when the cache button is clicked on a later page.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-28 17:46:20 -04:00
dsql 4d52ef7f50 fix: reject empty pages at construction (v0.1.1)
empty pages gave max_pages=0; get_page(0)/start() then IndexError'd on self.pages[0].
guard in __init__ with a clear ValueError instead of a deferred crash at render.

verified: ButtonPaginator([]) -> ValueError; non-empty and single-page intact.
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-28 15:51:40 -04:00
dsql 958920a6ba add package: pyproject + src
ButtonPaginator: button-navigated discord.py paginator (discord.ui.View)
over mixed page content (str/embed/file/attachment/dict) with per-page
custom buttons, jump-to-page modal, and a mention-cache primer button.
config-free — emojis injectable with unicode fallback, everything passed
at construction. src/ layout, hatchling build.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-24 21:36:43 -04:00
dsql 0a1f4f609f init: button paginator for discord.py
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-24 15:10:18 -04:00