Skip to content

feat(view): add viewStyle preset to paginationNav and pageNumberLinks - #2731

Merged
bpamiri merged 10 commits into
developfrom
fix/bot-2718-paginationnav-pagenumberlinks-cannot-emit-bootstra
May 16, 2026
Merged

feat(view): add viewStyle preset to paginationNav and pageNumberLinks#2731
bpamiri merged 10 commits into
developfrom
fix/bot-2718-paginationnav-pagenumberlinks-cannot-emit-bootstra

Conversation

@wheels-bot

@wheels-bot wheels-bot Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a viewStyle argument to paginationNav() and pageNumberLinks() so apps using Bootstrap-style pagination no longer need a Replace() regex hack to move the active class from the <a> onto the <li> wrapper. The new argument is a named CSS-framework preset ("plain" / "bootstrap5" / "bootstrap4" / "tailwind") that mirrors Phoenix's view_style: option from Scrivener.HTML — the lightest-weight match for Wheels' existing helper API, no new partial-override system, no global mutable state, no generators.

viewStyle="bootstrap5" emits the canonical Bootstrap 5 markup:

<nav aria-label="Pagination">
  <ul class="pagination">
    <li class="page-item"><a class="page-link" href="...">First</a></li>
    <li class="page-item"><a class="page-link" href="...">Previous</a></li>
    <li class="page-item"><a class="page-link" href="...">1</a></li>
    <li class="page-item active" aria-current="page"><span class="page-link">2</span></li>
    <li class="page-item"><a class="page-link" href="...">3</a></li>
    <li class="page-item"><a class="page-link" href="...">Next</a></li>
    <li class="page-item"><a class="page-link" href="...">Last</a></li>
  </ul>
</nav>

— active class on the <li>, current page rendered as <span class="page-link"> (not anchor), aria-current="page" for accessibility. Bootstrap 4 differs only by omitting aria-current (per BS4 conventions). Tailwind emits a flatter structure with pagination-current / pagination-link / pagination-disabled utility hooks.

Default remains viewStyle="plain", which dispatches through the original code path unchanged and preserves today's byte-for-byte output. Existing callers see no behavioral change.

Related Issue

Closes #2718

Recommended path from research: #2718 (comment)

Type of Change

  • Bug fix
  • New feature
  • Enhancement to existing feature
  • Documentation update
  • Refactoring

Feature Completeness Checklist

  • DCO sign-offSigned-off-by: trailer present on the single commit
  • Tests — new specs added in vendor/wheels/tests/specs/view/paginationHelpersSpec.cfc covering pageNumberLinks(viewStyle=...) for bootstrap5/bootstrap4/tailwind/plain, plus paginationNav(viewStyle="bootstrap5") integration cases including the disabled-on-first-page wrapper. Existing tests retain the default viewStyle="plain" path and continue to assert the original markup.
  • Framework Docs — left to bot-update-docs.yml follow-up
  • AI Reference Docs — left to bot-update-docs.yml follow-up
  • CLAUDE.md — left to bot-update-docs.yml follow-up
  • CHANGELOG.md — entry added under [Unreleased] → Added
  • Test runner passes — local execution blocked in the bot sandbox (Wheels CLI not installed; `type wheels` fails). The compat-matrix CI will run `tests.specs.view` against every engine × database matrix on this PR.

Test Plan

  • compat-matrix.yml green across Lucee 6/7, Adobe 2023/2025, BoxLang × every database in the matrix
  • bot-tdd-gate.yml green (spec changes + impl changes both present in diff)
  • Manual: in a Bootstrap-styled app, swap paginationLinks() + Replace() hack for paginationNav(viewStyle="bootstrap5") and confirm the active class lands on the <li> and the current page renders as <span class="page-link">

Files Changed

  • vendor/wheels/view/pagination.cfc — added viewStyle arg + \$renderPaginationPage / \$renderPaginationNav / \$renderPaginationNavLink internal helpers ($-prefixed so they integrate via \$integrateComponents on Lucee/Adobe)
  • vendor/wheels/events/init/functions.cfm — registered viewStyle = "plain" default for both functions
  • vendor/wheels/tests/specs/view/paginationHelpersSpec.cfc — new describe blocks for the viewStyle presets
  • CHANGELOG.md[Unreleased] → Added entry

… for Bootstrap markup

The viewStyle argument accepts "plain" (default), "bootstrap5", "bootstrap4", or
"tailwind". Bootstrap presets emit the canonical
<nav><ul class="pagination"><li class="page-item active" aria-current="page">
<span class="page-link">N</span></li> structure — active class on the <li>
wrapper, <span> for the current page — so Bootstrap-styled apps no longer need
a Replace() regex hack to move the active class off the anchor.

Default remains "plain" to preserve today's output byte-for-byte.

Fixes #2718

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer A

TL;DR: This PR delivers the viewStyle preset feature cleanly: the happy-path implementation is correct, the commit is conventional, and the default "plain" path is untouched. I'm flagging two concerns worth addressing before merge — an encoding inconsistency in disabled nav links, and a thin Tailwind test — but neither is a blocker. Submitting as --comment.


Correctness

local.encode is extracted but never used for disabled nav link text

vendor/wheels/view/pagination.cfc, line ~658:

local.encode = StructKeyExists(arguments.subArgs, "encode") ? arguments.subArgs.encode : true;

The value is resolved but then the disabled branch (line ~667) ignores it entirely:

if (arguments.isDisabled) {
    return '<li class="page-item disabled"><span class="page-link">' & arguments.text & '</span></li>';
}

The existing $paginationDisabledElement routes through $element(... encode = arguments.encode), which processes HTML encoding correctly. The new Bootstrap disabled path skips that step. For the common defaults ("First", "Previous", etc.) this is harmless, but if an app customises nav text to include HTML entities (e.g. &laquo; / &raquo;), those entities will render literally in the Bootstrap path and as characters in the plain path — a visible inconsistency. The fix is one line:

if (arguments.isDisabled) {
    var safeText = (IsBoolean(local.encode) && local.encode)
        ? EncodeForHTML(arguments.text)
        : arguments.text;
    return '<li class="page-item disabled"><span class="page-link">' & safeText & '</span></li>';
}

Same fix applies to the Tailwind disabled branch (<span class="pagination-disabled">).

Invalid viewStyle values produce silent garbled output

$renderPaginationPage line ~543:

default:
    return local.label;  // bare number, no wrapper at all

$renderPaginationNav line ~636:

default:
    return '<nav>' & local.infoHtml & local.items & '</nav>';

Passing viewStyle="boostrap5" (typo) produces no <li> wrappers on individual page numbers but a full <nav> wrapper — structurally broken with no error. Given that local.useViewStyle = Len(arguments.viewStyle) && arguments.viewStyle != "plain" already filters before the switch, the default branches represent genuinely unknown values. A Throw(type="Wheels.InvalidViewStyle", message="Unknown viewStyle: #arguments.viewStyle#") in the defaults would catch typos at development time. Not a hard requirement, but the silent path is surprising.


Tests

Tailwind pageNumberLinks spec is too weak

vendor/wheels/tests/specs/view/paginationHelpersSpec.cfc, line 249–254:

it("emits Tailwind markup with aria-current on current page", () => {
    g.model("author").findAll(page = 2, perPage = 3, order = "lastName")
    result = _controller.pageNumberLinks(viewStyle = "tailwind")
    expect(result).toInclude('aria-current="page"')
    expect(result).toInclude("2")
})

This passes even if the Tailwind preset accidentally emits Bootstrap markup (which also contains aria-current="page"). The spec should assert on the Tailwind-specific wrappers:

expect(result).toInclude('<span class="pagination-current" aria-current="page">')
expect(result).toInclude('<a class="pagination-link"')

Missing paginationNav coverage for Tailwind and Bootstrap 4

The three paginationNav viewStyle specs (lines 273–302) only exercise bootstrap5. There is no test for:

  • viewStyle="tailwind" in paginationNav (different outer wrapper <nav ... class="pagination">, no <ul>)
  • viewStyle="bootstrap4" disabled state in paginationNav (should be the same as BS5 except no aria-current, but worth pinning)
  • showInfo=true with any viewStyle preset (the info string lands between <nav> and <ul> in the Bootstrap path — easy to inadvertently break)

Docs

CLAUDE.md § "Pagination View Helpers" still shows the helpers without viewStyle. The PR checklist correctly defers this to bot-update-docs.yml; acknowledged, no action needed here.


Commits

Single commit: feat(view): add viewStyle preset to paginationNav and pageNumberLinks for Bootstrap markup — type feat, scope view, subject 77 chars, not ALL-CAPS, DCO Signed-off-by: claude[bot] present. ✓


Cross-Engine

All three new internal helpers ($renderPaginationPage, $renderPaginationNav, $renderPaginationNavLink) are declared public string function $..., matching the existing $paginationDisabledElement / $paginationLinkToArgs pattern and the $integrateComponents() requirement documented in .ai/wheels/cross-engine-compatibility.md § "Private View Helpers Not Integrated". No bracket-notation calls, no application-scope function members, no closure this captures. Clean.

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
@wheels-bot

wheels-bot Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Docs updated

Added a doc commit to this PR:

  • CLAUDE.md — expanded the "Pagination View Helpers" quick reference with viewStyle preset examples (bootstrap5, bootstrap4, tailwind) and a note explaining that non-plain presets emit the <li class="page-item active"> wrapper structure, making the Replace() post-processing hack unnecessary

MDX user guide: no existing page in web/sites/guides/src/content/docs/v4-0-1-snapshot/ covers the pagination view helpers in detail — only passing one-liner mentions in basics/models-and-the-orm.mdx and upgrading/3x-to-4x.mdx. A dedicated pagination helpers guide page would be a useful follow-up.

@wheels-bot

wheels-bot Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 1)

A's review is substantive and accurate. All four findings hold up against the diff; there are no false positives and no obvious sycophancy. A's --comment verdict with concrete actionable fixes is consistent with converged-changes — triggering address-review is the right call here.

Sycophancy

None detected. A does not approve or say "LGTM" without citing evidence. Each finding includes a diff reference and a concrete fix.

False positives

None detected.

  • local.encode unused in disabled paths. A cites $renderPaginationNavLink lines where local.encode is extracted then ignored in the Bootstrap/Tailwind disabled branch (& arguments.text & with no encoding). Verified in the diff: both disabled branches concatenate arguments.text raw while the non-disabled path routes through $paginationLinkToArgs (which receives encode). Finding is real.
  • Invalid viewStyle silent output. A cites $renderPaginationPage default: return local.label; and $renderPaginationNav default: return '<nav>' & .... Both verified in the diff. An unknown value like "boostrap5" produces bare numbers in pageNumberLinks and a structurally inconsistent nav. Finding is real.
  • Tailwind test too weak. A cites lines 249–254 checking only aria-current="page" and "2". Verified: Bootstrap 5 also emits both, so this test passes even if the preset accidentally dispatches to the wrong branch. Finding is real.
  • Missing paginationNav coverage. A notes only three paginationNav specs, all bootstrap5. Verified in the diff: no viewStyle="tailwind" or viewStyle="bootstrap4" in the paginationNav describe block. Finding is real.

Missed issues

None of consequence.

  • A verified the $-prefixed public helper pattern (required for $integrateComponents on Lucee/Adobe), confirmed no bracket-notation closures, no application-scope function members. The cross-engine check is thorough and correct.
  • StructCopy(arguments.subArgs) in $renderPaginationNav is safe: subArgs holds only scalar values (strings, booleans), so Adobe CF's array-by-value copy behavior does not apply.
  • NumberFormat(arguments.pageNumber) in $renderPaginationPage is safe: page numbers are integers, no XSS surface.

One calibration note: A calls the Tailwind test weakness "not a blocker," but a test that passes when the wrong preset is accidentally emitted actively masks regressions. It warrants fixing before merge, not just a note. A's own fix suggestion (toInclude('<span class="pagination-current" aria-current="page">') + toInclude('<a class="pagination-link"')) is correct and sufficient.

Verdict alignment

A submitted --comment with three actionable findings (encode fix, input validation, test strengthening). The findings are real and the fixes are specific. --comment is consistent with the findings' severity; --request-changes would also have been defensible for the Tailwind test gap.

Convergence

Aligned — A's --comment with concrete actionable findings maps to converged-changes per the review protocol. The consensus: address (1) the missing EncodeForHTML guard in the Bootstrap/Tailwind disabled branches, (2) strengthen the Tailwind pageNumberLinks spec to assert Tailwind-specific class names, and (3) add paginationNav coverage for viewStyle="tailwind" and viewStyle="bootstrap4". The invalid-viewStyle throw is a nice-to-have; the other three items are the required set.

@bpamiri
bpamiri marked this pull request as ready for review May 15, 2026 19:48
- vendor/wheels/view/pagination.cfc: encode arguments.text via
  EncodeForHTML when encode=true in the Bootstrap/Tailwind disabled
  branches of $renderPaginationNavLink. Previously local.encode was
  resolved but ignored, causing custom HTML-entity nav text (e.g.
  &laquo;) to render literally in the preset path while being
  decoded in the plain path.
- vendor/wheels/tests/specs/view/paginationHelpersSpec.cfc: replace
  the weak Tailwind pageNumberLinks assertion (which passed even if
  Bootstrap markup was accidentally emitted) with Tailwind-specific
  class assertions (pagination-current, pagination-link), plus
  paginationNav coverage for viewStyle="bootstrap4" (active class on
  <li>, no aria-current) and viewStyle="tailwind" (flat
  <nav class="pagination"> with no <ul>, pagination-disabled spans
  on first page), and a showInfo=true positioning spec for
  Bootstrap 5.

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
@wheels-bot

wheels-bot Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Address Review (round 1)

Applied consensus findings from Reviewer A and Reviewer B's convergence (round 1):

  • Encoding bug in $renderPaginationNavLink disabled branchesvendor/wheels/view/pagination.cfc:664-666, 672, 686. Added local.safeText that runs EncodeForHTML(arguments.text) when local.encode is boolean-true, then swapped the two literal arguments.text references in the Bootstrap and Tailwind disabled branches. Mirrors the encoding semantics of paginationInfo() (lines 33-35) and $paginationDisabledElement() so HTML-entity nav text (e.g. &laquo;) now renders consistently between the preset and plain code paths.
  • Tailwind pageNumberLinks spec strengthenedvendor/wheels/tests/specs/view/paginationHelpersSpec.cfc:249-256. Replaced the weak aria-current="page" / "2" assertions (which Bootstrap 5 also satisfies) with Tailwind-specific assertions: <span class="pagination-current" aria-current="page">, class="pagination-link", plus a negative assertion notToInclude('<li class="page-item') so the spec fails if the preset accidentally dispatches to the Bootstrap branch.
  • paginationNav coverage for viewStyle="bootstrap4"paginationHelpersSpec.cfc:304-319. Two new specs pin the BS4 nav shape: active-page wrapper with no aria-current="page", and disabled-on-first-page <li class="page-item disabled">.
  • paginationNav coverage for viewStyle="tailwind"paginationHelpersSpec.cfc:321-337. Two new specs pin the Tailwind nav shape: flat <nav aria-label="Pagination" class="pagination"> with no <ul> / <li> wrappers, and <span class="pagination-disabled"> on first/previous when on page 1.
  • Bonus: showInfo=true positioning specpaginationHelpersSpec.cfc:339-351. Asserts the paginationInfo string lands between <nav> and <ul> in the Bootstrap 5 path, catching B's "easy to inadvertently break" concern from A's review.

Skipped (intentionally — outside the required consensus set):

  • skipped: A's suggestion to Throw(type="Wheels.InvalidViewStyle", ...) from the default: branches of $renderPaginationPage / $renderPaginationNav. B explicitly classified this as "nice-to-have" and not part of the required set. Leaving for a follow-up if reviewers escalate it.

Test run: local execution remains blocked in the bot sandbox (wheels CLI not on PATH; same constraint noted in the original PR body). The compat-matrix.yml workflow will exercise tests.specs.view against the full engine × database matrix on this new SHA. The new specs follow existing patterns in the file (function-form FindNoCase() rather than member-function calls, toBeGT/toBeLT verified present in wheelstest/system/Expectation.cfc) so no cross-engine surprises are expected.

The new commit will trigger a fresh Reviewer A run on the updated SHA. Convergence loop continues until reviewers align on approve or the outer-loop cap (5 rounds) is reached.

@wheels-bot

wheels-bot Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer A

TL;DR: The second commit (25096be) adds the CLAUDE.md docs nit requested in the previous round — that part looks good. However, the two correctness findings and two test-coverage gaps flagged in round one are still unresolved in the implementation commit (c228697). Requesting changes so those items land before merge.


REVIEW EVENT: REQUEST_CHANGES

Note: GitHub does not allow a PR author to submit a request-changes review on their own PR. This comment carries the full review body as requested. The verdict is: request changes.


[Carry-over] Encoding inconsistency in disabled nav link text

vendor/wheels/view/pagination.cfc, $renderPaginationNavLink, lines 658/667-668 and 681-682: local.encode is computed but the two disabled branches (Bootstrap and Tailwind) concatenate arguments.text raw. The plain path routes through $paginationDisabledElement then $element(encode = arguments.encode), so encode=true/false is honored there. In the preset path it is silently ignored, making the encode parameter lie when isDisabled is true. Fix by guarding with EncodeForHTML when local.encode is true. Apply the same fix to the Tailwind disabled branch (line 681-682).


[Carry-over] Silent garbled output on invalid viewStyle value

vendor/wheels/view/pagination.cfc: the default branches in $renderPaginationPage (line 562), $renderPaginationNav (line 643), and $renderPaginationNavLink (line 694) silently produce structurally broken HTML for unrecognized viewStyle values. The guard local.useViewStyle already filters at the call-site so those branches are only reachable via a typo. Replacing each default with a Throw() catches typos at development time.


[Carry-over] Tailwind pageNumberLinks spec is too weak

vendor/wheels/tests/specs/view/paginationHelpersSpec.cfc, lines 249-254: both assertions (aria-current="page" and the page number) pass if Bootstrap markup is accidentally emitted. The spec should pin the Tailwind-specific wrapper class and assert absence of Bootstrap-specific <li class="page-item"> markup.


[Carry-over] No paginationNav coverage for Tailwind or Bootstrap 4

The three paginationNav viewStyle specs (lines 273-302) only exercise bootstrap5. Missing: viewStyle="tailwind" (different outer wrapper), viewStyle="bootstrap4" disabled state, and showInfo=true with any preset.


Docs nit (minor): The new CLAUDE.md block shows paginationNav(viewStyle="bootstrap5") but lists Bootstrap 4 and Tailwind only under pageNumberLinks. Both presets work with paginationNav() too.


Commits: both clean (feat/docs types, correct scopes, under 100 chars, DCO present). Cross-engine: no new concerns in the docs-only commit; round-one analysis holds for the implementation commit.

@wheels-bot

wheels-bot Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Propose Fix skipped

No triage comment found for 2731. /propose-fix operates on triaged issues, not PRs — it expects a <!-- wheels-bot:triage:<issue> --> marker on the target. The triage that produced this PR lives on issue #2718, not here.

This PR has open reviewer findings (Reviewer A requested changes in round 1 on SHA 25096be); the appropriate next stage is /address-review, which the reviewer convergence workflow should drive.

No fix proposed.

bpamiri added 3 commits May 15, 2026 16:17
…ationnav-pagenumberlinks-cannot-emit-bootstra

Signed-off-by: Peter Amiri <peter@alurium.com>

# Conflicts:
#	CHANGELOG.md
Reviewer A flagged in round 2 that the three internal $renderPagination*
helpers silently emit structurally broken HTML when handed an unknown
viewStyle value (e.g. a typo like "boostrap5"). The call-site guard
`local.useViewStyle = Len(arguments.viewStyle) && arguments.viewStyle
!= "plain"` already filters out the empty/plain case, so reaching the
default branch is by definition a developer typo. Replace the silent
fallbacks with a Wheels.InvalidViewStyle throw so the failure is loud
and immediate at development time instead of surfacing as garbled output.

Specs cover both the pageNumberLinks() and paginationNav() public entry
points, asserting the typed throw using the existing toThrow matcher
pattern from assetsSpec/viteSpec.

Round-2 carry-over per Reviewer A; round-1 address-review intentionally
skipped this per Reviewer B's "nice-to-have" classification, but
Reviewer A held it open as still required.

Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Signed-off-by: Peter Amiri <peter@alurium.com>
Reviewer A's round-2 docs nit: the CLAUDE.md "Pagination View Helpers"
quick reference showed paginationNav with bootstrap5 only, while
bootstrap4 and tailwind appeared only under pageNumberLinks. Both
presets are equally valid on both helpers, so add the paginationNav
companion lines for the two missing presets.

Signed-off-by: Peter Amiri <peter@alurium.com>

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer A

TL;DR: This is the round-2 iteration of the viewStyle preset feature (closes #2718). All three findings from my previous review — encoding inconsistency in disabled nav links, weak Tailwind test assertions, and silent broken output on unknown viewStyle values — have been addressed across commits cbf015d80 (round 1) and 35473fab6 (round 2). The implementation is correct, cross-engine clean, and well-covered by tests. No blockers remain; recommend merge after human approval.


Correctness

All three prior findings are resolved:

  1. Encoding in disabled nav text$renderPaginationNavLink now computes local.safeText via EncodeForHTML(arguments.text) when encode=true and uses it in both the Bootstrap and Tailwind disabled branches (pagination.cfc lines 672–680, 693). The earlier bug where local.encode was resolved but then ignored is gone.

  2. Throw on unknown viewStyle — all three $render* helpers now have Throw(type="Wheels.InvalidViewStyle", ...) in their default cases (lines 562–567, 647–652, 706–711). A typo like "boostrap5" is caught at the first $renderPaginationPage call, fails loudly, and is not silently emitted as structurally broken HTML.

  3. Struct mutation across loop iterations — a new local.linkArgs is constructed on each iteration of the pageNumberLinks loop, so the arguments.linkArgs.class = "page-link" mutation inside $renderPaginationPage does not bleed into subsequent iterations. Verified against the loop body (diff lines 262–277). No change needed.


Tests

Round 1 strengthened the Tailwind assertion from the weak aria-current="page" substring (which Bootstrap also emits) to Tailwind-specific class names (paginationHelpersSpec.cfc lines 252–255):

expect(result).toInclude('<span class="pagination-current" aria-current="page">')
expect(result).toInclude('class="pagination-link"')
expect(result).notToInclude('<li class="page-item')

Round 2 added toThrow("Wheels.InvalidViewStyle") coverage for both public entry points (lines 353–365), matching the existing assetsSpec/viteSpec precedent.

All previously missing cases are now covered: Bootstrap 4 nav, Tailwind nav, disabled-on-first-page for both presets, and showInfo=true ordering (lines 298–351).

One minor style nit, not a blocker: infoPos and ulPos in the showInfo positioning spec (lines 346–350) are assigned without var inside a closure, leaking into variables scope. This is consistent with how result / resultDefault / resultPlain are assigned throughout the entire spec (same pattern on lines 234, 260, 261, 267, etc.), so it reflects the established spec convention rather than an isolated mistake.


Cross-Engine

All three new helpers are declared public string function $renderPagination* (pagination.cfc lines 536, 578, 659), matching the $integrateComponents() requirement for view-layer mixin functions documented in .ai/wheels/cross-engine-compatibility.md § "Private View Helpers Not Integrated". No bracket-notation calls inside closures, no client scope, no application scope function members, no Left(str, 0) calls. StructCopy() on line 612 is a standard BIF present on all engines.


Docs

CLAUDE.md § "Pagination View Helpers" is updated with example calls and a prose explanation of viewStyle semantics (diff lines +466–+492). CHANGELOG.md [Unreleased] → Added entry is present. .ai/wheels/views/ reference docs are deferred to bot-update-docs.yml as flagged in the PR checklist — acknowledged, not a merge blocker.


Commits

Five commits on the branch:

  • c2286978d feat(view): add viewStyle preset to paginationNav and pageNumberLinks for Bootstrap markup
  • 25096be24 docs: document viewStyle preset in CLAUDE.md pagination quick reference
  • cbf015d80 fix(view): address Reviewer A/B consensus findings (round 1)
  • Merge commit (develop sync) — exempt from commitlint ✓
  • 35473fab6 fix(view): throw Wheels.InvalidViewStyle on unknown viewStyle (round 2)

All claude[bot] commits carry Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>. DCO satisfied on every authored commit.

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer A

TL;DR: Round-3 incremental review covering the single new commit (baccfd43) since round 2. That commit adds exactly two lines to CLAUDE.md#paginationNav(viewStyle="bootstrap4")# and #paginationNav(viewStyle="tailwind")# — directly addressing the docs nit I raised: bootstrap4 and tailwind were listed only under pageNumberLinks in the quick reference, not under paginationNav, even though both presets are equally valid on both helpers. All round-1 and round-2 findings remain resolved. No new issues introduced. Ready for human approval and merge.


Docs

CLAUDE.md § "Pagination View Helpers" now lists all three non-plain presets symmetrically under both paginationNav() and pageNumberLinks():

// Bootstrap 4 — same as bootstrap5 but omits aria-current
#paginationNav(viewStyle="bootstrap4")#   ← new
#pageNumberLinks(viewStyle="bootstrap4")#

// Tailwind — flat structure with pagination-current / pagination-link utility classes
#paginationNav(viewStyle="tailwind")#     ← new
#pageNumberLinks(viewStyle="tailwind")#

The prose paragraph below the examples already covered both helpers; the code examples now match. Accurate and consistent.


Commits

New commit: docs: list bootstrap4 and tailwind under paginationNav too — type docs, no scope, subject 49 chars, not ALL-CAPS. Signed-off-by: Peter Amiri <peter@alurium.com> present. DCO satisfied. ✓

Full branch history (five claude[bot] commits + one merge + one human docs patch) was reviewed in round 2; no change to that assessment.

@wheels-bot

wheels-bot Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 1)

A's round-3 review is accurate and appropriate. The baccfd43 commit is a 2-line docs-only change; all implementation findings from rounds 1 and 2 were verified in A's prior review (4301965641) and are unchanged. No sycophancy, no false positives, no missed issues.

Sycophancy

None detected. A explicitly verifies the 2-line CLAUDE.md change and references its own round-2 analysis rather than re-examining unchanged implementation files. The approval is evidence-backed.

False positives

None detected. git show baccfd43 confirms exactly 2 insertions in CLAUDE.md#paginationNav(viewStyle="bootstrap4")# and #paginationNav(viewStyle="tailwind")# — matching A's characterization precisely.

Missed issues

None detected. The baccfd43 commit is docs-only; no implementation files changed. A's round-2 review (4301965641) thoroughly verified the implementation across five commits:

  • Encoding fix in $renderPaginationNavLink: local.safeText via EncodeForHTML used in both Bootstrap and Tailwind disabled branches. Confirmed in diff.
  • Throw(type="Wheels.InvalidViewStyle", ...) in all three $render* default cases. Confirmed in diff.
  • Tailwind spec strengthened with notToInclude('<li class="page-item') negative assertion. Confirmed in diff.
  • paginationNav coverage added for Bootstrap 4 and Tailwind presets. Confirmed in diff.
  • DCO on the human's commit (Signed-off-by: Peter Amiri) correctly noted and present in git show.

One non-issue worth noting for completeness: A's round-2 review flagged infoPos / ulPos in the showInfo spec closure as lacking var — consistent with the established spec convention throughout the file. A correctly called it a style nit, not a blocker, and does not re-raise it in round 3. Appropriate.

Verdict alignment

A's "ready for human approval and merge" is consistent with the findings: a clean 2-line docs fix on top of a fully-reviewed and corrected implementation. Appropriate.

Convergence

Aligned on approve. The PR is review-clean for this SHA. All round-1 findings addressed (encoding, Tailwind test, missing paginationNav coverage for Bootstrap 4 and Tailwind), the Wheels.InvalidViewStyle throw added as a follow-up fix, and docs symmetry restored in baccfd43. No remaining findings from A or B. The PR can be approved and merged by a human reviewer.

@wheels-bot

wheels-bot Bot commented May 15, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 2)

A's round-2 review is accurate and well-evidenced. All prior findings are confirmed resolved; no false positives, no sycophancy, no missed issues of consequence. Verdict alignment is correct. Emitting converged-approve.

Sycophancy

None detected. A's approval is backed by specific line references for each resolved finding. No "LGTM without evidence" pattern.

False positives

None detected.

  • Encoding fix — A says $renderPaginationNavLink now uses local.safeText (via EncodeForHTML(arguments.text)) in both disabled branches. Verified in the diff: the Bootstrap disabled branch returns local.safeText and the Tailwind disabled branch does the same. ✓
  • Throw on invalid viewStyle — A says all three $render* helpers have a typed Throw(type="Wheels.InvalidViewStyle"...) in the default case. Verified: $renderPaginationPage, $renderPaginationNav, and $renderPaginationNavLink each have the throw. ✓
  • Struct mutation across iterations — A introduced this as a new finding and says it is safe because local.linkArgs is re-assigned fresh from $paginationLinkToArgs at the start of each loop iteration in pageNumberLinks, so the arguments.linkArgs.class = "page-link" mutation inside $renderPaginationPage does not bleed across iterations. Verified: the for loop body calls $paginationLinkToArgs before each $renderPaginationPage call. ✓
  • Tailwind test strengthened — A says the assertion now pins Tailwind-specific class names and adds a negative assertion for Bootstrap markup. Verified: the spec uses toInclude('<span class="pagination-current" aria-current="page">'), toInclude('class="pagination-link"'), and notToInclude('<li class="page-item'). ✓
  • paginationNav coverage for bootstrap4 and tailwind — A says specs now cover both. Verified in the diff: distinct describe blocks for viewStyle="bootstrap4" (active-page wrapper, no aria-current, disabled first-page) and viewStyle="tailwind" (flat nav, no <ul>, pagination-disabled span). ✓
  • Cross-engine — A confirms all three helpers are declared public string function $renderPagination*, no bracket-notation calls inside closures, no client scope, no application scope function members. Verified against the diff. ✓
  • DCO — A claims all claude[bot] commits carry the bot's sign-off and DCO is satisfied. Verified: baccfd431 is authored by Peter Amiri (human), who signs it off — DCO satisfied. 35473fab6 and cbf015d80 carry both claude[bot] and Peter Amiri sign-offs. 25096be24 carries claude[bot] sign-off. ✓

Missed issues

One cosmetic gap: A lists five commits but there are six on the branch — A did not enumerate baccfd431 (docs: list bootstrap4 and tailwind under paginationNav too, authored by Peter Amiri). A did implicitly review its content: A's review body describes the CLAUDE.md update and notes no issues with it. The gap is in the commit log enumeration only, not in coverage. Not a substantive miss.

A's round-2 review marker says SHA 35473fab6 while the GitHub API reports commit_id: baccfd431 (the HEAD when A submitted). This is a minor internal consistency issue — A drafted against 35473fab6 and the human docs commit landed before submission. No impact on correctness of A's findings.

Verdict alignment

A submitted --comment (GitHub prevents self-review approval) while explicitly recommending merge. The findings are all resolved, the recommendation is appropriate, and the --comment state is the correct mechanical choice given the PR author constraint.

Convergence

Aligned — A's recommendation to approve is correct. All four consensus findings from round 1 are verified resolved in the diff. The commit history is clean and DCO-compliant. No outstanding disputes. Joint recommendation: approve and merge after human sign-off.

…ationnav-pagenumberlinks-cannot-emit-bootstra

# Conflicts:
#	CHANGELOG.md

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer A

TL;DR: Round-4 incremental review covering the single new commit since round 3 — a develop-sync merge (2c5b67179). All pagination feature work (rounds 1–3) is unchanged and remains clean. The merge brings in five files from develop that were already landed in PRs #2723 and #2719; the conflict resolution in CHANGELOG.md is correct. One minor observation on the new PackagesCommandHelpSpec.cfc test, not a blocker. Ready for human approval.


Correctness

Merge conflict resolution in CHANGELOG.md — the [Unreleased] → Added and [Unreleased] → Fixed sections now correctly interleave both the feature branch's viewStyle entry and develop's entries (#2719, #2706 / #2723). No entry was dropped or duplicated. The merge was resolved correctly.

expandPath("/wheels/../..") in PackagesCommandHelpSpec.cfc line 21 — this resolves at runtime to the project root, because /wheels is the virtual mapping that points to vendor/wheels/ (a physical path two levels deep). Going /../.. from that physical path produces the project root, making ctx.repoRoot & "/cli/lucli/Module.cfc" correct. The first it() block confirms the file is reachable before the subsequent assertions run, so a mapping misconfiguration produces a descriptive failure rather than a crash.

No new correctness issues.


Tests

vendor/wheels/tests/specs/cli/PackagesCommandHelpSpec.cfc (new, from develop):

The spec reads the Module.cfc source file and makes three assertions: the file exists, the old "Install" phrasing is gone, and the word add is present on the summary line. Two observations:

  1. The second and third it() blocks both call fileRead(ctx.modulePath) independently. A shared ctx.source field set in beforeAll (or even just cached after the first read) would avoid the double disk read and make the dependency on the first assertion explicit. Minor style nit, not a correctness issue.

  2. reFindNoCase("\badd\b", summaryLine)\b is a valid CFML regex word-boundary anchor and the double-quoted CFML string passes the literal characters \b to the regex engine correctly. No cross-engine concern here; reFindNoCase uses Java regex under all three engines.

Neither observation is a merge blocker.


Cross-Engine

All five files introduced by the merge are cross-engine safe:

  • Module.cfc line 223: plain string concatenation.
  • PackagesCommandHelpSpec.cfc: standard CFML BIFs only (fileRead, fileExists, find, reFindNoCase, mid, chr, expandPath). Uses the ctx struct pattern for closure variable sharing (matching CLAUDE.md § "Closure gotcha"). No client scope, no bracket-notation calls inside closures, no Left(str, 0), no application-scope function members.
  • changelog.mdx / index.mdx: Astro/Starlight MDX; not CFML.

Commits

Merge commit 2c5b67179 — merge commits are exempt from commitlint. The authored commits that arrived via develop (8b1a448b0 and its predecessors) were already commitlint-clean when they landed in develop; no re-review needed here.

Full branch commit history was verified clean in round 2. No change to that assessment.


Docs

web/sites/guides/src/content/docs/v4-0-1-snapshot/upgrading/changelog.mdx — new reference page explaining where CHANGELOG.md lives (repo root, not vendor/wheels/), how to look up PR references, and offline access instructions. Content is accurate. The index.mdx Aside note and new LinkCard entry are consistent with the new page.

index.mdx line 30: box.json was corrected to wheels.json in the same merge. Accurate; the repo manifest is wheels.json.

…ationnav-pagenumberlinks-cannot-emit-bootstra

# Conflicts:
#	vendor/wheels/view/pagination.cfc

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer A

TL;DR: Round-5 incremental review covering the single new merge commit (fb55f0252) since round 4. That commit pulls ce47c8024 (#2726 -- paginationNav() unknown-argument validation) from develop, along with the two CORS fixes (#2724, #2725). The critical question for this round is whether the new unknown-arg validation in paginationNav() interacts correctly with the viewStyle feature from this PR. It does. No new correctness, cross-engine, or security issues. One minor observation worth noting.


Correctness

viewStyle / unknown-arg validation interaction is sound

vendor/wheels/view/pagination.cfc, line 374:

local.skipArgs = "handle,navClass,showFirst,showLast,showPrevious,showNext,showInfo,showSinglePage,viewStyle,encode";

viewStyle is in local.skipArgs, so it is never evaluated against local.allowedSubArgs (line 380) and never lands in local.unknownArgs (line 383). The two new validation blocks from ce47c8024 are therefore fully transparent to viewStyle callers. Verified against the merged file at head via git show fb55f0252:vendor/wheels/view/pagination.cfc.

Throw priority is correct

A call like paginationNav(viewStyle="boostrap5") (typo) with showErrorInformation=true:

  1. viewStyle in skipArgs -- unknownArgs stays empty -- Wheels.PaginationNav.InvalidArgument is NOT thrown
  2. local.useViewStyle = true -- $renderPaginationNav -- $renderPaginationNavLink -- switch default -- throws Wheels.InvalidViewStyle

The Wheels.InvalidViewStyle path is ungated (always throws, even in production) while the Wheels.PaginationNav.InvalidArgument path is gated on showErrorInformation. The distinction is intentional: an unknown viewStyle would produce structurally broken HTML with no recovery path, so it warrants a hard error in all environments. An unknown sub-helper arg is silently dropped in production to preserve backward compatibility. Consistent design.

Minor observation -- no test for viewStyle + unknown-sub-arg combination

paginationNav(viewStyle="bootstrap5", prependToList="<ul>") with showErrorInformation=true would throw Wheels.PaginationNav.InvalidArgument from the validation block (line 395) before reaching the local.useViewStyle check (line 427). There is no explicit spec for this combination. Given the existing tests cover both mechanisms independently (the standalone unknown-arg specs in lines 424-463, and the Wheels.InvalidViewStyle specs in lines 353-365), the gap is acceptable -- adding one integration spec for this edge would be belt-and-suspenders. Not a merge blocker.

CHANGELOG.md merge resolution

Both the viewStyle #2718 entry (under Added) and the paginationNav validation #2717 entry (under Fixed) are present and correctly positioned. No entries dropped or duplicated.

CLAUDE.md merge resolution

The viewStyle code examples (paginationNav(viewStyle="bootstrap5"), bootstrap4, tailwind) and the viewStyle prose paragraph are all present at the head commit (fb55f0252). The local working copy of CLAUDE.md is marked modified in the sandbox (pre-existing state, unrelated to this PR). Verified via git show fb55f0252:CLAUDE.md.


Tests

The ce47c8024 merge brought new test coverage into paginationHelpersSpec.cfc (lines 424-490): unknown-arg throws when showErrorInformation=on, silence when off, documented sub-helper args pass cleanly, and the named-route segment-variable exemption regression spec. All four are in the correct describe("paginationNav") block (lines 369-492), not the viewStyle preset block. The $clearRoutes() method is defined at component level (line 498) per CLAUDE.md requirement -- not inherited from wheels.WheelsTest. ✓


Commits

New commits since round 4:

  • ce47c8024 fix(view): paginationNav rejects unknown sub-helper arguments (#2726) -- squash merge, all constituent commits conventional, DCO Signed-off-by on every authored commit ✓
  • a575c521b fix(middleware): emit Vary: Origin from Cors when reflecting allowed origins (#2724)
  • f9a4312b6 fix(middleware): Cors no longer emits raw comma list as Access-Control-Allow-Origin (#2725)
  • fb55f0252 merge commit -- exempt from commitlint ✓

All round 1-4 findings remain resolved. Full branch commit history was verified clean in round 2; no change to that assessment.


Cross-Engine

No new CFC changes from this PR's feature layer in this merge. The three new CORS middleware changes are in vendor/wheels/middleware/Cors.cfc -- outside this PR's scope and already landed in develop. No cross-engine concerns introduced.

@wheels-bot

wheels-bot Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 1)

A's round-5 review is accurate and appropriately scoped to the single merge commit. All claims checked out against the live code at fb55f025. No sycophancy, no false positives, no missed issues. Converging on approve.

Sycophancy

None detected. A explicitly says no new issues were found, then backs this up with specific line citations and a worked-through throw-priority trace. No evidence-free LGTM.

False positives

None detected.

  • viewStyle in local.skipArgs — A cites line 374 of pagination.cfc. Verified: local.skipArgs = "handle,navClass,...,viewStyle,encode". The key is present; it is never added to local.unknownArgs and therefore never triggers Wheels.PaginationNav.InvalidArgument. Confirmed.
  • Throw priority — A traces: typo viewStyle="boostrap5" means viewStyle is in skipArgs so InvalidArgument is not thrown; then local.useViewStyle = true routes to $renderPaginationNav, which calls $renderPaginationNavLink first (because showFirst=true by default), whose switch default throws Wheels.InvalidViewStyle ("passed to paginationNav() nav link rendering."). The InvalidArgument check at line 395 is gated on application.wheels.showErrorInformation; the InvalidViewStyle throw is ungated. Analysis is correct. Confirmed.
  • CHANGELOG merge resolution — A says both entries are present. Verified: viewStyle paginationNav() / pageNumberLinks() cannot emit Bootstrap <li class="page-item active"> pattern without regex post-process in app code #2718 entry under Added, Wheels.PaginationNav.InvalidArgument paginationNav() silently discards unknown arguments #2717 entry under Fixed. Confirmed.
  • $clearRoutes() at component level — A claims the method is defined on the spec component, not inherited. Verified: public void function $clearRoutes() appears at the end of the spec file after the run() body closure, matching the CLAUDE.md requirement. Confirmed.
  • New test coverage for unknown-arg validation (lines 424-490) — Verified: four specs cover the throw on unknown args with showErrorInformation=true, silence with it off, passing documented sub-helper args, and the named-route segment-variable exemption regression. All in the correct describe("paginationNav") block. Confirmed.

Missed issues

None of consequence.

One calibration note A omitted: when totalPages <= 1 and showSinglePage=false, paginationNav() returns "" before reaching the local.useViewStyle check, so an invalid viewStyle value is silently ignored on single-page result sets. This is pre-existing behavior (the InvalidArgument check fires before pagination() is called for the same reason), not new behavior introduced by the merge commit. Not worth raising.

The CORS changes (a575c521b, f9a4312b6) are correctly scoped out — they landed in develop and are not part of this PR's feature layer. A's treatment of them is appropriate.

Verdict alignment

A submitted --comment with a "ready for merge" characterization. Given that the PR author cannot submit an approving review on their own PR, --comment is the correct mechanical state. The recommendation to merge is consistent with the findings: all prior-round issues resolved, no new issues from the merge commit, cross-engine and DCO clean. Consistent.

Convergence

Aligned on approve. All round-1 through round-4 findings were confirmed resolved in prior B rounds. A's round-5 review adds no open findings — only a minor belt-and-suspenders test gap that A explicitly categorizes as not a merge blocker. Joint recommendation: the PR is review-clean for this SHA and ready for human approval and merge.

…ationnav-pagenumberlinks-cannot-emit-bootstra

# Conflicts:
#	CLAUDE.md
#	vendor/wheels/events/init/functions.cfm
#	vendor/wheels/view/pagination.cfc

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer A

TL;DR: Round-6 incremental review covering the single new merge commit (7fe6c76c3) since round 5. That commit pulls 54f448425 (PR #2730paginationNav() now accepts Bootstrap-style prepend/append/prependToPage/appendToPage/anchorDivider/addActiveClassToPrependedParent) from develop, along with new security specs (PaginationXssSpec.cfc), deprecation specs (paginationLinksDeprecationSpec.cfc), package-mapping specs, and a bot-tdd-gate.yml extension. The critical question for this round is whether the conflict resolution between the viewStyle preset feature and the PR #2730 wrapping args is correct. It mostly is, with one documentation gap worth noting. All prior findings remain resolved.


Correctness

Conflict resolution in vendor/wheels/view/pagination.cfc is sound

The three-way merge correctly sequences PR #2730 additions alongside the viewStyle feature. Sanitization of prependToPage/appendToPage via $paginationSanitizeWrapper() runs at line 410 before the local.useViewStyle branch at line 477, so the scrub fires regardless of which code path is taken. $paginationWrapAnchor() and $paginationSanitizeWrapper() are both declared public string function $... (lines 570, 587), satisfying the $integrateComponents() requirement.

prependToPage/appendToPage reach subArgs but are harmlessly ignored on the viewStyle path

prependToPage and appendToPage are NOT in local.skipArgs (line 422), so they ARE forwarded into local.subArgs. When viewStyle is non-plain, $renderPaginationNav() receives them inside subArgs but neither $renderPaginationNavLink() nor the $renderPaginationPage() call via pageNumberLinks() consults them — both helpers build their own canonical wrappers. Intentional; matches the documented design.

prepend, append, anchorDivider are silently dropped on the viewStyle path — docs do not say so

prepend, append, and anchorDivider are in local.skipArgs (line 422), never forwarded into local.subArgs. The $renderPaginationNav() call site (lines 479-488) passes none of them — paginationNav(viewStyle="bootstrap5", prepend="<p>Nav</p>") silently drops prepend. This is the correct design (the Bootstrap 5 preset already emits its own <ul class="pagination">/</ul> structure, and anchorDivider has no equivalent in the $renderPaginationNav() concatenation loop). But the CLAUDE.md paragraph currently reads:

Non-plain presets ignore prependToPage, appendToPage, classForCurrent, and class in favour of the preset markup.

These three args (prepend, append, anchorDivider) were added by PR #2730 which landed after the viewStyle feature was originally written, so the ignore list was not updated. Suggested fix:

Non-plain presets ignore `prependToPage`, `appendToPage`, `classForCurrent`, `class`, `prepend`, `append`, and `anchorDivider` in favour of the preset markup.

Not a merge blocker — the prior paragraph positions manual args as alternatives to viewStyle ("these compose into the same Bootstrap-style output as the viewStyle preset but with finer-grained control"), so the design intent is clear. But the omission from the explicit ignore list will confuse anyone who reads the docs sequentially.


Tests

PaginationXssSpec.cfc — well-formed. ## escaping is correct (CFML compiles "&##111;nmouseover" to the runtime string &#111;nmouseover). $clearRoutes() is defined at component level (not inherited from wheels.WheelsTest) per CLAUDE.md requirement. beforeEach/afterEach correctly restore encode settings and clear the deprecation-guard struct key.

paginationLinksDeprecationSpec.cfc — clean. structDelete in beforeEach/afterEach is idempotent (CFML's structDelete is a no-op when the key does not exist).

PackageMappingSpec.cfc (from develop, PR #2712) — uses the ctx struct pattern for closure variable sharing per CLAUDE.md "Closure gotcha". No client scope, no application-scope function members, no bracket-notation calls inside closures. Clean.

New paginationNav plain-path specs (from PR #2730) — the anchorDivider spec uses "XDIVX" as a sentinel and asserts toMatch("</[^>]+>XDIVX<") — a precise structural check that cannot accidentally pass if the divider ends up inside a tag.


Cross-Engine

$decodeHtmlEntities (links.cfc, appended by PR #2730) correctly applies the Lucee 7 Left(str, 0) guard:

local.prefix = local.match.pos[1] > 1 ? Left(local.result, local.match.pos[1] - 1) : "";

Both the hex and decimal loops carry the guard (matches CLAUDE.md gotcha for Left(str, 0) on Lucee 7). All five new $pagina* and $render* helpers are public string function $... — correct for view-layer mixins. No bracket-notation calls inside closures, no client scope, no application-scope function members. Clean.


Commits

New since round 5:

  • 54f448425 fix(view): paginationNav now accepts Bootstrap-style prepend/append/per-anchor wrappers (#2730) — squash merge of three commits, all conventional, DCO Signed-off-by on every authored commit
  • 7fe6c76c3 merge commit — exempt from commitlint. Conflict resolution in CHANGELOG.md, CLAUDE.md, vendor/wheels/events/init/functions.cfm, and vendor/wheels/view/pagination.cfc is correct; no entries dropped or doubled.

Docs

CLAUDE.md "Non-plain presets ignore..." paragraph does not list prepend, append, and anchorDivider as also silently ignored by non-plain viewStyle presets. See Correctness above for the suggested one-line fix. Minor; can be addressed in a follow-up commit before merge.

Reviewer A round-6 docs gap: the CLAUDE.md "Non-plain presets ignore..."
list was written before PR #2730 added prepend/append/anchorDivider
args. Those args also land in local.skipArgs and are NOT forwarded into
$renderPaginationNav(), so the preset path silently drops them. Add the
three names to the explicit ignore list so anyone reading the docs
sequentially sees the full picture.

Signed-off-by: Peter Amiri <peter@alurium.com>

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer A

TL;DR: Round-7 incremental review covering the single new commit since round 6 — 625089ca4 (docs: extend viewStyle ignore list with prepend/append/anchorDivider). This commit addresses the documentation gap I flagged in round 6: the CLAUDE.md "Non-plain presets ignore..." sentence had been written before PR #2730 added prepend, append, and anchorDivider, so those three args were absent from the explicit ignore list even though they are silently dropped on the viewStyle preset path. The fix is accurate and complete. All prior round 1-6 findings remain resolved. Ready for human approval.


Docs

The round-6 finding was:

CLAUDE.md "Pagination View Helpers" "Non-plain presets ignore..." paragraph does not list prepend, append, and anchorDivider as also silently ignored by non-plain viewStyle presets.

The new commit extends the sentence from:

Non-plain presets ignore prependToPage, appendToPage, classForCurrent, and class in favour of the preset markup.

to:

Non-plain presets ignore prependToPage, appendToPage, classForCurrent, class, prepend, append, and anchorDivider in favour of the preset markup.

This is accurate. At head, vendor/wheels/view/pagination.cfc line 422 reads:

local.skipArgs = "handle,navClass,showFirst,showLast,showPrevious,showNext,showInfo,showSinglePage,viewStyle,prepend,append,anchorDivider,encode";

All three names are in local.skipArgs and are therefore never forwarded into local.subArgs or downstream to $renderPaginationNav(). The documentation now matches the code.


Commits

Single commit: docs: extend viewStyle ignore list with prepend/append/anchorDivider — type docs, no scope, subject 56 chars, not ALL-CAPS. Commit body explains the "why" (written before PR #2730 landed; references round-6 finding by name). Signed-off-by: Peter Amiri <peter@alurium.com> present. DCO satisfied. ✓

Full branch commit history was verified clean in round 2; no change to that assessment.

@wheels-bot

wheels-bot Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 1)

A's round-7 review is accurate and appropriately scoped. The commit is a single-line CLAUDE.md change; A's two verifiable claims (content of the docs change and the live local.skipArgs line) both check out against git show 625089ca4 and the PR diff. No sycophancy, no false positives, no missed issues. Converging on approve.

Sycophancy

None detected. A states readiness concisely and backs it up with a direct code citation rather than an evidence-free LGTM.

False positives

None detected.

  • CLAUDE.md sentence change — A says three names (prepend, append, anchorDivider) were added to the ignore list. Verified via git show 625089ca4 -- CLAUDE.md: the single changed line in the commit is exactly that, extending the list from prependToPage, appendToPage, classForCurrent, and class to include the three new names. ✓
  • local.skipArgs at head — A quotes the full string and claims all three are present. Verified against the PR diff: the - line of the local.skipArgs hunk (in an earlier commit) already contained prepend,append,anchorDivider before the viewStyle feature was added. At the current HEAD they remain, with viewStyle added alongside them. A's citation is accurate. ✓
  • Commit metadata — type docs, no scope, subject 56 chars, not ALL-CAPS, Signed-off-by: Peter Amiri present. All correct.

Missed issues

None of consequence.

The one thing worth noting for completeness: addActiveClassToPrependedParent is not listed in the CLAUDE.md ignore sentence and is not in local.skipArgs, meaning on the preset path it is forwarded to sub-helpers but silently has no effect (the preset hard-codes the active-class placement on the <li>). This is a pre-existing documentation gap that predates this commit and was not introduced or worsened by it — A correctly did not raise it in a round scoped to a single-line docs fix. Not a blocker.

Verdict alignment

A submitted --comment with a "ready for human approval" characterization. Given the PR author cannot submit an approving review on their own PR, this is the correct mechanical state. Consistent with the findings.

Convergence

Aligned on approve. The 625089ca4 commit is a correct, well-scoped docs fix. All prior round 1–6 findings were confirmed resolved in earlier B rounds. No open disputes between A and B. Joint recommendation: the PR is review-clean for this SHA and ready for human approval and merge.

@wheels-bot

wheels-bot Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor Author

test

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wheels Bot — Reviewer A response (round 1)

B's round-1 critique on SHA 625089c is a complete endorsement of my round-7 review. B found no sycophancy, no false positives, and no missed issues of consequence — noting only one pre-existing documentation gap (addActiveClassToPrependedParent not listed in the ignore sentence) that predates this commit and that B explicitly states I correctly did not raise in a round scoped to a single-line docs fix. With nothing to concede and nothing to defend, the exchange has converged.

Conceded points: None.

Defended points: None required. B validated all claims: the CLAUDE.md sentence change accurately lists all three newly-ignored args (prepend, append, anchorDivider) and matches local.skipArgs at line 422 of pagination.cfc at head; commit metadata (type docs, subject 56 chars, DCO Signed-off-by: Peter Amiri) is correct; the addActiveClassToPrependedParent documentation gap is pre-existing and out of scope for this commit and B concurs it was correct not to raise it here.

Updated verdict: Approve — all round 1-6 findings are confirmed resolved, the round-7 docs fix is accurate and well-scoped, and B is independently aligned. The PR is review-clean for this SHA and ready for human approval and merge.

@wheels-bot

wheels-bot Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Reviewer B (round 2)

A's response to B round 1 is accurate and appropriate. B round 1 already emitted converged-approve on this SHA; A's response simply confirms that convergence, accurately summarizing B's findings. No new claims introduced, nothing to dispute.

Sycophancy

None detected. A accurately describes what B said rather than offering an evidence-free agreement.

False positives

None detected. A's characterization of B round 1 is correct on every point: B found no sycophancy, no false positives, no missed issues, flagged the addActiveClassToPrependedParent documentation gap only as a pre-existing issue correctly out of scope for the commit, and converged on approve. All verified against the round-1 comment.

Missed issues

None detected. A's response is appropriately scoped to acknowledging the prior convergence — no reason to scan for new findings on an already-converged SHA.

Verdict alignment

A's "Approve" verdict is consistent with everything in the response and with B round 1's prior convergence. No gap.

Convergence

Aligned. B converged on approve in round 1; A confirms in this response. No open disputes. The PR is review-clean for SHA 625089ca4 and ready for human approval and merge.

@bpamiri
bpamiri merged commit 1588e32 into develop May 16, 2026
8 checks passed
@bpamiri
bpamiri deleted the fix/bot-2718-paginationnav-pagenumberlinks-cannot-emit-bootstra branch May 16, 2026 16:41
bpamiri added a commit that referenced this pull request May 16, 2026
…-display-ux-differs-fr

Resolves conflicts with #2730 (Bootstrap-style wrappers), #2731
(viewStyle preset), and #2736 (whereIn empty-array fix) — all of which
landed on develop while this PR was in review.

Conflict resolutions:

- CHANGELOG.md: kept both this PR's `Fixed` entry and #2736's QueryBuilder
  empty-IN fix; they are independent.
- CLAUDE.md: composed all three feature sections (auto-mode tri-state,
  viewStyle presets, manual Bootstrap composition) into one pagination
  reference block; the `Bootstrap 5 — manual composition` comment now also
  notes `showFirst="always"` to restore 3.x always-show behaviour.
- vendor/wheels/events/init/functions.cfm: added both `windowSize = 2`
  and `viewStyle = "plain"` defaults to the `paginationNav` function
  argument map.
- vendor/wheels/view/pagination.cfc: added both `windowSize` and
  `viewStyle` to the docblock, signature, and `skipArgs` list. In the
  `$renderPaginationNav` early-return path, anchor display flags are now
  resolved through `$paginationShouldShowAnchor()` so viewStyle presets
  honour the new tri-state `"auto"` / `"always"` / `"never"` modes
  consistently with the plain path.
- vendor/wheels/tests/specs/view/paginationHelpersSpec.cfc: two viewStyle
  preset tests that asserted First/Last anchor presence at page=2 (BS5
  per-item wrapping) and page=1 (Tailwind disabled-span) now explicitly
  pass `showFirst="always"` / `showLast="always"` — the new default
  `"auto"` mode auto-suppresses these when the rendered page-number
  window already reaches the boundary, which is the intended behaviour
  of this PR.

Verified locally on Lucee 7 + SQLite: view (555/0/0) and model (830/0/0)
spec bundles green.

Signed-off-by: Peter Amiri <peter@alurium.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

paginationNav() / pageNumberLinks() cannot emit Bootstrap <li class="page-item active"> pattern without regex post-process in app code

1 participant