Conversation
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
forge-select | 7b2a9f7 | Commit Preview URL Branch Preview URL |
Aug 02 2026, 05:48 AM |
|
Warning Review limit reached
Next review available in: 52 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (3)
📝 WalkthroughWalkthroughForgeSelect 0.7.0 adds guarded selection and asynchronous option creation, template sanitization, cursor-based remote pagination, indexed and virtualized rendering, lifecycle benchmark budgets, mobile layout fixes, and npm Trusted Publishing configuration. ChangesForgeSelect 0.7.0
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant ForgeSelect
participant remote_ts as remote.ts
participant AjaxRequest as AjaxConfig.request
User->>ForgeSelect: request more remote options
ForgeSelect->>remote_ts: load current cursor
remote_ts->>AjaxRequest: pass query, page, signal, and cursor
AjaxRequest-->>remote_ts: return options and nextCursor
remote_ts-->>ForgeSelect: return normalized result
ForgeSelect->>ForgeSelect: cache cursor and append options
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/ForgeSelect.ts (1)
1175-1237: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
beforeSelectguard is bypassed when a created tag matches an existing option by label or value.
activateNavIteminvokesthis.opts.beforeSelect?.(item.option)before selecting an option from the list.createTag's "existing option" branch (matched by label) andaddCreatedOption's "duplicate" branch (matched by value) both callthis.selectValue(...)directly, without checkingbeforeSelect.This means the
beforeSelectguard is not enforced when a user pastes text, or presses Enter withallowCreate, that exactly matches an existing option's label/value. The newdocs/examples.mdguard example (beforeSelect: (option) => !option.meta?.archived) is specifically intended to block selecting an archived option — but a user who types that archived option's exact label and hits "Enter" (or pastes it) bypasses the guard entirely, sincecreateTag/addCreatedOptionselect it directly. This affects both the paste-multi-create handler (lines 843-861) andcreateFromQuery(lines 1239-1266), both of which route throughcreateTag.Invoke
beforeSelectin both "existing" branches, consistent withactivateNavItem.🐛 Proposed fix
private createTag(label: string): TagCreation | undefined | Promise<TagCreation | undefined> { const trimmed = label.trim(); if (!trimmed) return undefined; const existing = this.findOptionByLabel(trimmed); if (existing) { if (this.selected.includes(existing.value)) return undefined; if (this.opts.multiple && !this.canSelectOption(existing)) { this.announceMaximum(existing); return undefined; } + if (this.opts.beforeSelect?.(existing) === false) return undefined; this.selectValue(existing.value, false); return { option: existing, created: false }; } ... private addCreatedOption(option: Option): TagCreation | undefined { if (this.opts.multiple && !this.canSelectOption(option)) { this.announceMaximum(option); return undefined; } const duplicate = this.findOption(option.value); if (duplicate) { if (this.selected.includes(duplicate.value)) return undefined; + if (this.opts.beforeSelect?.(duplicate) === false) return undefined; this.selectValue(duplicate.value, false); return { option: duplicate, created: false }; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/ForgeSelect.ts` around lines 1175 - 1237, Enforce the beforeSelect guard in both existing-option paths: update createTag’s label-matched branch and addCreatedOption’s value-duplicate branch to invoke this.opts.beforeSelect?.(option) before selecting, and abort when it rejects. Preserve the existing duplicate, maximum-selection, and return-value behavior for allowed selections.
🧹 Nitpick comments (4)
.github/workflows/release.yml (1)
100-102: 🔒 Security & Privacy | 🔵 TrivialVerify npm Trusted Publishing for every package target.
These commands now rely on OIDC instead of
NPM_TOKEN. Configure and test the npm Trusted Publisher relationship separately forforge-select,forge-select-react, andforge-select-vue, using repositorycmm-cmm/ForgeSelectand workflow.github/workflows/release.yml.id-token: writeenables the token request but does not create the npm-side relationship. npm also requires the configured workflow identity to match exactly. (docs.npmjs.com)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release.yml around lines 100 - 102, Verify and configure npm Trusted Publisher relationships for each release target—forge-select, forge-select-react, and forge-select-vue—using repository cmm-cmm/ForgeSelect and workflow .github/workflows/release.yml, then test each corresponding npm publish path. Ensure the npm-side workflow identity matches exactly; retain the existing publish commands and id-token permission.src/types.ts (1)
29-37: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win
AjaxConfig.urlfunction form can't receive the new pagination cursor.requestandparamsboth gained an optionalcursorparameter for cursor-based pagination, buturl's callback signature was left at(query: string, page: number) => string, so thebuildUrlconsumer has no cursor to forward whenurlis a function.
src/types.ts#L29-L37: extendurl?: string | ((query: string, page: number, cursor?: string) => string);to matchrequest/params.src/remote.ts#L3-L11: once the type is extended, passcursorthrough inreturn ajax.url(query, page, cursor);.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/types.ts` around lines 29 - 37, Extend the AjaxConfig.url callback signature in src/types.ts (lines 29-37) to accept an optional cursor, and update buildUrl in src/remote.ts (lines 3-11) to forward cursor when invoking ajax.url; keep string URL handling unchanged.src/ForgeSelect.ts (1)
1682-1790: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReduce
renderRow's cognitive complexity.Static analysis reports cognitive complexity of 25 against a limit of 15 for
renderRow. Theswitchhandles nine row kinds inline with repeated attribute setup. Extract eachcasebody (or at least the "option" case, which is the largest) into a dedicated private method (for examplerenderOptionRow(li, row)) to bring the function under the limit and make each row kind independently testable.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/ForgeSelect.ts` around lines 1682 - 1790, Reduce cognitive complexity in renderRow by extracting the inline row-kind rendering logic, especially the large "option" branch, into dedicated private helpers such as renderOptionRow and corresponding helpers for other cases as needed. Keep renderRow responsible for element reset, dispatching by row.kind, and returning the element, while preserving all existing attributes, classes, selection, highlighting, and expansion behavior.Source: Linters/SAST tools
tests/forge-select.test.ts (1)
192-207: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAdd regression coverage for
beforeSelectduring tag creation.This test exercises
beforeSelectonly through a direct list click (optionEls()[0].click()). It doesn't cover the paste or Enter-to-create path where the typed/pasted label matches an existing option — which is exactly the scenario flagged insrc/ForgeSelect.ts(createTag/addCreatedOption, lines 1175-1237), wherebeforeSelectis currently not invoked. Once that fix lands, add a case whereselect.setSearchQuery(existingBlockedLabel)followed by "Enter" (or a paste of that label) is expected to be rejected bybeforeSelect.Would you like me to draft this test?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/forge-select.test.ts` around lines 192 - 207, Extend the “supports selection guards and async tag creation” test to cover creation via Enter or paste when the query matches the existing blocked option. Set the search query to the blocked label, trigger the create flow, and assert beforeSelect rejects it by keeping getValue() null; retain the existing successful async creation assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/release.yml:
- Line 43: Update the npm installation command in the release workflow to use an
exact reviewed npm version instead of the ^11.15.0 semver range, and add
--ignore-scripts to prevent lifecycle scripts from running during this
privileged publish path.
In `@CHANGELOG.md`:
- Line 10: Complete the changelog reference definitions for the 0.7.0 release:
add a matching [0.7.0] link targeting the v0.6.0...v0.7.0 comparison, and update
the [Unreleased] reference to start at v0.7.0 so it excludes the released
changes.
In `@scripts/benchmark.mjs`:
- Around line 40-48: Update the initialization sampling loop around ForgeSelect
and initSamples to verify DOM cleanup after every mount/destroy pair. Capture
the relevant selector count before creating each instance and after
instance.destroy() and mount.remove(), then assert or record that the count
returns to its prior value; ensure the baseline used by
residualNodesAfterDestroy cannot include leaks from earlier samples.
In `@src/ForgeSelect.ts`:
- Around line 1682-1697: Update the attribute-reset list in renderRow to include
data-selection-state, ensuring recycled rows remove stale indeterminate state
before new row attributes are applied.
In `@styles/forge-select.css`:
- Line 269: Keep the logical margin in styles/forge-select.css unchanged, and
update the forge-select DOM setup to copy the mounted component’s direction to
the .forge-select--portal-host when dropdownParent is document.body. In
tests/e2e/forge-select.spec.ts lines 72-94, add an assertion that the portal
host’s computed direction is rtl; no direct CSS change is needed.
---
Outside diff comments:
In `@src/ForgeSelect.ts`:
- Around line 1175-1237: Enforce the beforeSelect guard in both existing-option
paths: update createTag’s label-matched branch and addCreatedOption’s
value-duplicate branch to invoke this.opts.beforeSelect?.(option) before
selecting, and abort when it rejects. Preserve the existing duplicate,
maximum-selection, and return-value behavior for allowed selections.
---
Nitpick comments:
In @.github/workflows/release.yml:
- Around line 100-102: Verify and configure npm Trusted Publisher relationships
for each release target—forge-select, forge-select-react, and
forge-select-vue—using repository cmm-cmm/ForgeSelect and workflow
.github/workflows/release.yml, then test each corresponding npm publish path.
Ensure the npm-side workflow identity matches exactly; retain the existing
publish commands and id-token permission.
In `@src/ForgeSelect.ts`:
- Around line 1682-1790: Reduce cognitive complexity in renderRow by extracting
the inline row-kind rendering logic, especially the large "option" branch, into
dedicated private helpers such as renderOptionRow and corresponding helpers for
other cases as needed. Keep renderRow responsible for element reset, dispatching
by row.kind, and returning the element, while preserving all existing
attributes, classes, selection, highlighting, and expansion behavior.
In `@src/types.ts`:
- Around line 29-37: Extend the AjaxConfig.url callback signature in
src/types.ts (lines 29-37) to accept an optional cursor, and update buildUrl in
src/remote.ts (lines 3-11) to forward cursor when invoking ajax.url; keep string
URL handling unchanged.
In `@tests/forge-select.test.ts`:
- Around line 192-207: Extend the “supports selection guards and async tag
creation” test to cover creation via Enter or paste when the query matches the
existing blocked option. Set the search query to the blocked label, trigger the
create flow, and assert beforeSelect rejects it by keeping getValue() null;
retain the existing successful async creation assertions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 8928e7ee-5fd4-4319-967f-21f4db01ad98
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (20)
.github/workflows/ci.yml.github/workflows/release.ymlCHANGELOG.mddocs/api-reference.mddocs/benchmarks.mddocs/examples.mdpackage.jsonpackages/react/CHANGELOG.mdpackages/vue/CHANGELOG.mdscripts/benchmark.mjssite/assets/site.csssrc/ForgeSelect.tssrc/index.tssrc/option-renderer.tssrc/remote.tssrc/types.tsstyles/forge-select.csstests/e2e/forge-select.spec.tstests/e2e/site-navigation.spec.tstests/forge-select.test.ts
| .forge-select__option--indeterminate::after { | ||
| content: "−"; | ||
| margin-left: auto; | ||
| margin-inline-start: auto; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Propagate the source direction to the portal host.
When dropdownParent is document.body, the portal host is outside the <main dir="rtl"> subtree. It inherits LTR direction. Line 269 then resolves margin-inline-start as LTR, and the test passes without testing RTL portal content.
styles/forge-select.css#L269-L269: Keep the logical margin, but copy the mounted component direction to the portal host during DOM setup.tests/e2e/forge-select.spec.ts#L72-L94: Assert that.forge-select--portal-hosthas computeddirection: rtl.
Proposed fix
if (portalParent) {
this.portalHost = document.createElement("div");
this.portalHost.className = "forge-select forge-select--portal-host";
+ this.portalHost.style.direction = getComputedStyle(this.root).direction;
this.portalHost.dataset.theme = this.opts.theme; const box = await page.locator(".forge-select--portal-host").boundingBox();
if (!box) throw new Error("Portal geometry was unavailable");
+ await expect(page.locator(".forge-select--portal-host")).toHaveCSS("direction", "rtl");
expect(box.x).toBeGreaterThanOrEqual(0);📍 Affects 2 files
styles/forge-select.css#L269-L269(this comment)tests/e2e/forge-select.spec.ts#L72-L94
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@styles/forge-select.css` at line 269, Keep the logical margin in
styles/forge-select.css unchanged, and update the forge-select DOM setup to copy
the mounted component’s direction to the .forge-select--portal-host when
dropdownParent is document.body. In tests/e2e/forge-select.spec.ts lines 72-94,
add an assertion that the portal host’s computed direction is rtl; no direct CSS
change is needed.
- Enforce beforeSelect when createTag/addCreatedOption match an existing option by label or value (paste-multi-create bypassed the guard). - Clear stale data-selection-state on recycled dropdown rows. - Extract renderRow's option branch into renderOptionRow to cut cognitive complexity. - Pin release.yml's npm CLI to an exact version and add --ignore-scripts. - Propagate the mounted component's direction to the portalled dropdown host so RTL layouts render correctly outside the source subtree. - Forward the pagination cursor to AjaxConfig.url's function form. - Check DOM cleanup after every init sample in the benchmark script so a destroy leak tied to large datasets can't hide in the baseline. - Complete the CHANGELOG 0.7.0 reference links.
Bump the forge-select dependency to ^0.7.0 so the new selection-guard, async-creation, sanitization, and cursor-pagination options are exposed through the existing typed props/options surface.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
.github/workflows/release.yml (2)
98-102: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winComplete npm Trusted Publisher setup before release.
Configure an npm Trusted Publisher for
core,packages/react, andpackages/vueto trust this repository and workflow. This publish step relies only onid-token: writeandnpm publish --provenance; without the registry-side Trusted Publishing allowlist, release publishes will fail.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release.yml around lines 98 - 102, Complete npm Trusted Publisher configuration for the core, packages/react, and packages/vue packages, allowing this repository and release workflow to publish each package. Ensure the registry-side trust allowlist matches the workflow identity used by the npm publish commands and existing id-token: write/provenance setup.
31-35: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winPin release actions to immutable commit SHAs.
actions/checkout@v6andactions/setup-node@v6are mutable references, and this workflow hasid-token: writeto publish to npm. Pin both actions to full commit SHAs, plus the npm CLI install to a pinned URL/checksum, then keep the current tag as a reviewed dependency comment.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release.yml around lines 31 - 35, Update the release workflow’s actions/checkout and actions/setup-node steps to use reviewed full commit SHAs instead of version tags, retaining each current tag in a dependency comment. Replace the npm CLI installation with a pinned URL and checksum, preserving the existing release behavior and id-token permissions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/release.yml:
- Around line 43-44: Update the global npm installation step to use the
checked-in dist.integrity checksum for npm@11.15.0, ensuring the package is
verified rather than only version-pinned. Keep the subsequent npm --version
check and make it fail unless the installed version is exactly 11.15.0;
alternatively, use a repository-controlled artifact that preserves the required
Trusted Publishing flow.
In `@packages/react/CHANGELOG.md`:
- Line 14: Update the changelog dependency wording in
packages/react/CHANGELOG.md:14-14 and packages/vue/CHANGELOG.md:14-14 to state
that forge-select versions in the 0.7.x range are required, matching the ^0.7.0
dependency constraint; make the same wording change at both affected sites.
---
Outside diff comments:
In @.github/workflows/release.yml:
- Around line 98-102: Complete npm Trusted Publisher configuration for the core,
packages/react, and packages/vue packages, allowing this repository and release
workflow to publish each package. Ensure the registry-side trust allowlist
matches the workflow identity used by the npm publish commands and existing
id-token: write/provenance setup.
- Around line 31-35: Update the release workflow’s actions/checkout and
actions/setup-node steps to use reviewed full commit SHAs instead of version
tags, retaining each current tag in a dependency comment. Replace the npm CLI
installation with a pinned URL and checksum, preserving the existing release
behavior and id-token permissions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 0738e0b7-7af6-40c6-9ac7-9c67214cde37
📒 Files selected for processing (12)
.github/workflows/release.ymlCHANGELOG.mdpackages/react/CHANGELOG.mdpackages/react/package.jsonpackages/vue/CHANGELOG.mdpackages/vue/package.jsonscripts/benchmark.mjssrc/ForgeSelect.tssrc/remote.tssrc/types.tstests/e2e/forge-select.spec.tstests/forge-select.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
- tests/e2e/forge-select.spec.ts
- CHANGELOG.md
- tests/forge-select.test.ts
- scripts/benchmark.mjs
- src/remote.ts
- src/types.ts
- src/ForgeSelect.ts
| npm install --global --ignore-scripts npm@11.15.0 | ||
| npm --version |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
expected='11.15.0'
test "$(npm view "npm@${expected}" version)" = "$expected"
npm view "npm@${expected}" engines dist.integrity --jsonRepository: cmm-cmm/ForgeSelect
Length of output: 958
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== release workflow relevant actions =="
sed -n '1,90p' .github/workflows/release.yml || true
echo
echo "== package metadata =="
for f in package.json package-lock.json; do
if [ -f "$f" ]; then
echo "--- $f ---"
sed -n '1,80p' "$f"
else
echo "--- $f: not found ---"
fi
doneRepository: cmm-cmm/ForgeSelect
Length of output: 8032
Pin the global npm install to a checked-in integrity checksum.
npm@11.15.0 is exact, but npm install --global still resolves the CLI executable from the public registry. Install it with the checked-in dist.integrity digest, fail if npm --version is not 11.15.0, or switch to a repository-controlled artifact that supports the required Trusted Publishing flow.
🧰 Tools
🪛 zizmor (1.28.0)
[warning] 43-43: ad-hoc installation of packages (adhoc-packages): installs a package outside of a lockfile
(adhoc-packages)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release.yml around lines 43 - 44, Update the global npm
installation step to use the checked-in dist.integrity checksum for npm@11.15.0,
ensuring the package is verified rather than only version-pinned. Keep the
subsequent npm --version check and make it fail unless the installed version is
exactly 11.15.0; alternatively, use a repository-controlled artifact that
preserves the required Trusted Publishing flow.
Source: Linters/SAST tools
- package-lock.json was out of sync after bumping the wrapper packages' forge-select dependency to ^0.7.0, breaking npm ci in CI. - Pin actions/checkout and actions/setup-node in release.yml to reviewed commit SHAs instead of a floating major tag, since this workflow holds id-token: write for npm publishing. - Correct the react/vue changelog wording: ^0.7.0 pins the 0.7.x range, not "0.7.0 or newer".
|



Summary
mainwas 3 commits behinddev. Syncing per request — bringing dev's latest work into main:8f28a23— Add async data safeguards and cut 0.7.0: substantial feature work (tree-selectchildren, dropdown portaling, sortable tags,requiredvalidation,beforeSelect/beforeUnselect/beforeCreateguards,createOption,sanitizeTemplate, custom search scoring/highlighting, AJAX pagination/retry/prefetch/caching safeguards, RTL-safe CSS logical properties, new e2e coverage) plus the0.7.0version bump, CHANGELOG entry, and updated docs.40a35b2— Use npm trusted publishing for releases: migratesrelease.ymlfrom a long-livedNPM_TOKENsecret to npm's OIDC-based Trusted Publishing (id-token: write, npm CLI ≥11.15.0).e94bfa7— Fix OIDC npm publish invocation: follow-up fix restoring--provenance --access publicon thenpm publishcalls under the new trusted-publishing flow.Test plan
dev(typecheck, lint, format, coverage, workspace build/test, site build + validation, npm pack validation, npm audit, Playwright across Chromium/Firefox/WebKit, benchmark budgets).npm run typecheckandnpm run lintlocally againstdev's tip — clean.release.ymlworkflow) for the next release to actually publish — this is an out-of-band step only the account owner can complete, not verifiable from CI.Generated by Claude Code
Summary by CodeRabbit
New Features
Documentation
Bug Fixes
Performance