Repository navigation
[Customer Portal] Improvements in the Security Center - #575
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 49 minutes and 51 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds product-level metadata (productName, productVersion, componentType, updateLevel) across backend types, OpenAPI, mapping and service logic (including bulk fetch + caching for large limits). Frontend gains product/version filters, client-side filtering/pagination, expanded table/DETAILS UI and updated constants/types. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client(UI)
participant Frontend as Frontend (Table)
participant Service as Backend Service
participant Entity as Entity Store
participant Cache as Org Cache
Client->>Frontend: request vulnerabilities (limit > 50)
Frontend->>Service: FETCH_ALL_REQUEST (limit large)
Service->>Cache: lookup cache(key=filters)
alt cache hit
Cache-->>Service: cached aggregated result
else cache miss
Service->>Entity: count(filters, limit=1)
Entity-->>Service: totalCount
loop batches (50)
Service->>Entity: search(filters, limit=50, offset=N)
Entity-->>Service: batchResults
Service->>Service: map batchResults
Service-->>Service: aggregate batches
end
Service->>Cache: store aggregated result (ttl=12h)
Cache-->>Service: ack
end
Service-->>Frontend: aggregated response (totalRecords, items)
Frontend->>Frontend: client-side filter/search/pagination
Frontend-->>Client: rendered page
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 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 (3)
apps/customer-portal/webapp/src/features/security/components/ProductVulnerabilitiesTable.tsx (1)
150-153:⚠️ Potential issue | 🟠 MajorClear stale product versions when the product changes.
Because version options are scoped to
filters.productName, changing the product should also clearproductVersion; otherwise the next request can send a version from the previous product and return an empty list.Proposed dependent-filter reset
const handleUpdateFilter = (field: string, value: string | number) => { - setFilters((prev) => ({ ...prev, [field]: value })); + setFilters((prev) => ({ + ...prev, + [field]: value, + ...(field === "productName" ? { productVersion: "" } : {}), + })); setPage(0); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/features/security/components/ProductVulnerabilitiesTable.tsx` around lines 150 - 153, The handleUpdateFilter function should clear dependent productVersion when productName changes: update handleUpdateFilter (used to setFilters and setPage) so that when field === 'productName' you also remove or reset filters.productVersion (e.g., set it to empty string or undefined) before calling setFilters, ensuring the request won't send a stale version; keep setPage(0) behavior unchanged.apps/customer-portal/webapp/src/features/security/utils/productVulnerabilitiesTable.ts (1)
21-36:⚠️ Potential issue | 🟡 MinorUpdate the active-filter JSDoc to include product filters.
The implementation now counts
productNameandproductVersion, but the doc still says only search + severity. As per learnings, ensure JSDoc is attached to the correct function/signature and kept accurate.Proposed doc update
/** - * Counts search + severity filters for the product vulnerabilities table header. + * Counts search, severity, product name, and product version filters for the product vulnerabilities table header.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/features/security/utils/productVulnerabilitiesTable.ts` around lines 21 - 36, The JSDoc for countProductVulnerabilityTableActiveFilters is outdated (it only mentions search + severity) while the implementation also counts productName and productVersion; update the JSDoc block above the function to list search, severity, productName, and productVersion as counted filters and ensure the doc is directly attached to the countProductVulnerabilityTableActiveFilters function signature so tooling picks it up.apps/customer-portal/backend/modules/types/types.bal (1)
857-863:⚠️ Potential issue | 🔴 CriticalDuplicate
componentTypeandupdateLeveldeclarations inProductVulnerabilityResponse.
ProductVulnerability(lines 824–854) declares optionalcomponentType?andupdateLevel?at lines 844 and 846.ProductVulnerabilityResponseincludes*ProductVulnerabilityat line 858, which pulls those fields into the record. Re-declaring them at lines 860–862 is redundant and causes a duplicate field compile error in Ballerina. The entity-sideProductVulnerabilityResponseinapps/customer-portal/backend/modules/entity/types.bal(lines 1455–1458) correctly omits these re-declarations, relying only on*ProductVulnerability.🛠 Proposed fix
# Product vulnerability information. public type ProductVulnerabilityResponse record {| *ProductVulnerability; - # Type of the component - string componentType?; - # Update level for the vulnerability - string updateLevel?; |};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/backend/modules/types/types.bal` around lines 857 - 863, ProductVulnerabilityResponse currently embeds *ProductVulnerability and then re-declares the optional fields componentType and updateLevel, causing duplicate-field compile errors; to fix it, remove the redundant declarations of componentType? and updateLevel? from the ProductVulnerabilityResponse record so it only contains "*ProductVulnerability" and any truly new fields, leaving ProductVulnerability (which already defines componentType? and updateLevel?) as the single source of those fields.
🧹 Nitpick comments (2)
apps/customer-portal/webapp/src/features/security/types/security.ts (1)
83-92: Inconsistent nullability betweenproductName/productVersionandcomponentType/updateLevel.
productName/productVersionare typed asstring | null, while the sibling optional fields added in the same PR (componentType?: string,updateLevel?: string) are plain optional strings. The corresponding Ballerina backend fields are all declared the same way (string productName?;,string componentType?;— optional, non-nullable). Pick one convention (preferably?: stringto match backend, since these are serialized as absent when not present rather thannull) and apply it consistently.♻️ Proposed alignment
- productName?: string | null; - productVersion?: string | null; + productName?: string; + productVersion?: string;If backend can genuinely return
nullfor these (verify againstutils.balmapping), then also makecomponentType/updateLevelstring | nullfor consistency.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/features/security/types/security.ts` around lines 83 - 92, The ProductSecurity type mixes `string | null` (productName, productVersion) with optional strings (componentType?, updateLevel?), causing inconsistent nullability; update the declarations to use the backend-aligned optional-string convention by changing `productName: string | null` and `productVersion: string | null` to `productName?: string` and `productVersion?: string` (or alternatively, if backend truly returns null, change `componentType?: string` and `updateLevel?: string` to `componentType: string | null` and `updateLevel: string | null`), and adjust any code that constructs or reads these fields to treat absent values as undefined rather than null; use the property names productName, productVersion, componentType, and updateLevel to locate and update the type in the Security/Product-related type definitions.apps/customer-portal/webapp/src/features/security/components/VulnerabilityDetailsContent.tsx (1)
283-341: Hardcoded UI strings — use existing constants.The file already imports named constants for most labels (e.g.,
VULNERABILITY_DETAILS_PRODUCT_NAME_LABEL,VULNERABILITY_DETAILS_JUSTIFICATION_LABEL). However,"Product Vulnerabilities"(line 284),"CVE"/"Vulnerability ID"(lines 329–330), and"Severity"(line 340) are inlined. This is inconsistent and makes future i18n/rewording harder. Please extract them tosecurityConstants.tsalongside the existingVULNERABILITY_DETAILS_*constants.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/features/security/components/VulnerabilityDetailsContent.tsx` around lines 283 - 341, The component VulnerabilityDetailsContent.tsx contains hardcoded UI strings ("Product Vulnerabilities", "CVE", "Vulnerability ID", "Severity"); add corresponding constants in securityConstants.ts (e.g., VULNERABILITY_DETAILS_PRODUCT_VULNERABILITIES_LABEL, VULNERABILITY_DETAILS_CVE_LABEL, VULNERABILITY_DETAILS_VULNERABILITY_ID_LABEL, VULNERABILITY_DETAILS_SEVERITY_LABEL) following the existing VULNERABILITY_DETAILS_* naming pattern, export them, and update VulnerabilityDetailsContent.tsx to import and use those constants in place of the inline strings (replace the Typography title and the FieldBox label props and the Severity caption).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/customer-portal/backend/openapi.yaml`:
- Around line 4639-4644: Update the OpenAPI schema for
ProductVulnerabilitySearchPayload so its filters object declares the missing
fields productName and productVersion (both type: string, with appropriate
descriptions) to match the response and frontend behavior; locate the
ProductVulnerabilitySearchPayload definition and add productName and
productVersion into the filters property (also ensure the same additions are
made to the other occurrence referenced around the second block for lines
~4697-4715 so both payload definitions stay in sync).
In
`@apps/customer-portal/webapp/src/features/security/components/ProductVulnerabilitiesList.tsx`:
- Around line 70-76: Headers in ProductVulnerabilitiesList.tsx are mismatched to
the rendered fields: the header for the column rendering row.updateLevel
currently reads "WSO2 Resolution" and the "Usecase" header has a typo; update
those TableCell labels to reflect the actual data (e.g. change "WSO2 Resolution"
to "Update Level" or another label matching row.updateLevel, and change
"Usecase" to "Use Case") and make the same corrections in the later duplicate
header block (the block referenced around lines 173–181) so headers align with
the rendered fields.
In
`@apps/customer-portal/webapp/src/features/security/components/ProductVulnerabilitiesTable.tsx`:
- Around line 35-39: OPTIONS_FETCH_REQUEST currently caps option fetching to
PRODUCT_VULNERABILITIES_OPTIONS_FETCH_LIMIT (500), causing missing filter
options; replace this one-shot request with either (a) a call to a dedicated
metadata/options endpoint that returns all distinct product name/version
options, or (b) implement paginated fetching that loops using the response's
totalRecords and pagination cursors (fetching subsequent pages until all records
are retrieved) and then deduplicate/aggregate product name/version options
before treating the set as complete; update the code paths that use
OPTIONS_FETCH_REQUEST (the option-population logic referenced around the current
constant and the blocks noted in the comment) to use the new metadata call or
the paginated-aggregate function and remove the hard cap constant usage.
In
`@apps/customer-portal/webapp/src/features/security/components/ProductVulnerabilitiesTableSkeleton.tsx`:
- Around line 36-47: PRODUCT_VULNERABILITIES_TABLE_COLUMN_COUNT is set to 10 but
the skeleton and actual data rows render 12 TableCell columns; update the
constant PRODUCT_VULNERABILITIES_TABLE_COLUMN_COUNT in securityConstants.ts from
10 to 12 so colSpan usage in ProductVulnerabilitiesList.tsx (error/empty state
rows) matches the 12-column layout used by
ProductVulnerabilitiesTableSkeleton.tsx and the data rows, ensuring aligned
table width.
In
`@apps/customer-portal/webapp/src/features/security/components/VulnerabilityDetailsContent.tsx`:
- Around line 404-422: The WSO2 Resolution FieldBox is incorrectly bound to
data.useCase; change its value prop to the correct field (e.g.,
value={data.resolution ?? VULNERABILITY_DETAILS_NOT_APPLICABLE_LABEL}) if WSO2
should show the existing resolution, or to value={data.wso2Resolution ??
VULNERABILITY_DETAILS_NOT_APPLICABLE_LABEL} if a new WSO2-specific field exists
on the ProductVulnerability model; update the mapping/props accordingly and
ensure you don't create an unintended duplicate with the existing "Resolution"
FieldBox (if both are required, add data.wso2Resolution to the model/mapping and
use that in the VULNERABILITY_DETAILS_WSO2_RESOLUTION_LABEL FieldBox).
---
Outside diff comments:
In `@apps/customer-portal/backend/modules/types/types.bal`:
- Around line 857-863: ProductVulnerabilityResponse currently embeds
*ProductVulnerability and then re-declares the optional fields componentType and
updateLevel, causing duplicate-field compile errors; to fix it, remove the
redundant declarations of componentType? and updateLevel? from the
ProductVulnerabilityResponse record so it only contains "*ProductVulnerability"
and any truly new fields, leaving ProductVulnerability (which already defines
componentType? and updateLevel?) as the single source of those fields.
In
`@apps/customer-portal/webapp/src/features/security/components/ProductVulnerabilitiesTable.tsx`:
- Around line 150-153: The handleUpdateFilter function should clear dependent
productVersion when productName changes: update handleUpdateFilter (used to
setFilters and setPage) so that when field === 'productName' you also remove or
reset filters.productVersion (e.g., set it to empty string or undefined) before
calling setFilters, ensuring the request won't send a stale version; keep
setPage(0) behavior unchanged.
In
`@apps/customer-portal/webapp/src/features/security/utils/productVulnerabilitiesTable.ts`:
- Around line 21-36: The JSDoc for countProductVulnerabilityTableActiveFilters
is outdated (it only mentions search + severity) while the implementation also
counts productName and productVersion; update the JSDoc block above the function
to list search, severity, productName, and productVersion as counted filters and
ensure the doc is directly attached to the
countProductVulnerabilityTableActiveFilters function signature so tooling picks
it up.
---
Nitpick comments:
In
`@apps/customer-portal/webapp/src/features/security/components/VulnerabilityDetailsContent.tsx`:
- Around line 283-341: The component VulnerabilityDetailsContent.tsx contains
hardcoded UI strings ("Product Vulnerabilities", "CVE", "Vulnerability ID",
"Severity"); add corresponding constants in securityConstants.ts (e.g.,
VULNERABILITY_DETAILS_PRODUCT_VULNERABILITIES_LABEL,
VULNERABILITY_DETAILS_CVE_LABEL, VULNERABILITY_DETAILS_VULNERABILITY_ID_LABEL,
VULNERABILITY_DETAILS_SEVERITY_LABEL) following the existing
VULNERABILITY_DETAILS_* naming pattern, export them, and update
VulnerabilityDetailsContent.tsx to import and use those constants in place of
the inline strings (replace the Typography title and the FieldBox label props
and the Severity caption).
In `@apps/customer-portal/webapp/src/features/security/types/security.ts`:
- Around line 83-92: The ProductSecurity type mixes `string | null`
(productName, productVersion) with optional strings (componentType?,
updateLevel?), causing inconsistent nullability; update the declarations to use
the backend-aligned optional-string convention by changing `productName: string
| null` and `productVersion: string | null` to `productName?: string` and
`productVersion?: string` (or alternatively, if backend truly returns null,
change `componentType?: string` and `updateLevel?: string` to `componentType:
string | null` and `updateLevel: string | null`), and adjust any code that
constructs or reads these fields to treat absent values as undefined rather than
null; use the property names productName, productVersion, componentType, and
updateLevel to locate and update the type in the Security/Product-related type
definitions.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c3fd6994-55c7-410e-991e-86b8a7a72e80
📒 Files selected for processing (12)
apps/customer-portal/backend/modules/entity/types.balapps/customer-portal/backend/modules/types/types.balapps/customer-portal/backend/openapi.yamlapps/customer-portal/backend/utils.balapps/customer-portal/webapp/src/features/security/components/ProductVulnerabilitiesFilters.tsxapps/customer-portal/webapp/src/features/security/components/ProductVulnerabilitiesList.tsxapps/customer-portal/webapp/src/features/security/components/ProductVulnerabilitiesTable.tsxapps/customer-portal/webapp/src/features/security/components/ProductVulnerabilitiesTableSkeleton.tsxapps/customer-portal/webapp/src/features/security/components/VulnerabilityDetailsContent.tsxapps/customer-portal/webapp/src/features/security/constants/securityConstants.tsapps/customer-portal/webapp/src/features/security/types/security.tsapps/customer-portal/webapp/src/features/security/utils/productVulnerabilitiesTable.ts
…nalysis - Add Product Name and Product Version filter dropdowns to the Component Analysis table; filter state resets version when product changes - BFF transparently batches entity service calls (50/batch) when limit > 50, allowing the frontend to fetch all ~1500 records in a single request - All filtering, pagination, and sorting now done client-side for instant UX - 12-hour in-memory BFF cache for bulk vulnerability fetches (shared across users; keyed by server-side filter params) - Remove maxValue: 50 constraint on Pagination.limit in entity types so the BFF can accept large-limit requests from the frontend - Fix truncation in Use Case, Justification, and Resolution table columns - Fix WSO2 Resolution field copy-paste bug in VulnerabilityDetailsContent - Extract hardcoded strings to constants throughout the security feature - Add productName/productVersion to search filter types (backend + frontend) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
apps/customer-portal/webapp/src/features/security/components/VulnerabilityDetailsContent.tsx (1)
408-433:⚠️ Potential issue | 🟠 Major
WSO2 Resolutionstill duplicatesResolution.Both fields bind to
data.resolution, so the page shows the same value twice under different labels. Unless the API is adding a separate WSO2-specific field, this should be a single field or the WSO2 box should bind to the intended distinct property.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/features/security/components/VulnerabilityDetailsContent.tsx` around lines 408 - 433, The WSO2 Resolution FieldBox is incorrectly bound to data.resolution and duplicates the Resolution field; update the WSO2 box (label VULNERABILITY_DETAILS_WSO2_RESOLUTION_LABEL in VulnerabilityDetailsContent) to bind to the correct property (e.g., data.wso2Resolution or the API-provided WSO2-specific field) or remove the WSO2 FieldBox if no separate field exists so it no longer duplicates the FieldBox using data.resolution; ensure the other Resolution FieldBox remains bound to data.resolution and keep multiline/label props as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/customer-portal/backend/service.bal`:
- Around line 2992-2996: The cache key for responses is currently built in the
cacheKey variable using only payload.filters.searchQuery, severityId, and
statusId; include payload.filters.productName, payload.filters.productVersion,
and payload.filters.sortBy so different product filters or sort orders produce
distinct cache entries. Update the string template that builds cacheKey (the
cacheKey variable construction) to append productName, productVersion, and
sortBy (using the same null-coalescing pattern ?: "" as the existing fields) so
cache lookups and inserts account for those additional filter/sort dimensions.
- Around line 2999-3009: The cache-hit branch for productVulnerabilityCache.get
returns the full aggregated cachedEntry but still echoes reqOffset/reqLimit,
causing wrong pages; modify the productVulnerability cache-return logic (where
cachedEntry is used to build the types:ProductVulnerabilitySearchResponse) to
honor reqOffset and reqLimit by slicing the cachedEntry array to the window
[reqOffset, reqOffset + reqLimit) (or by starting aggregation at reqOffset and
collecting reqLimit entries) and set productVulnerabilities to that slice while
keeping totalRecords = cachedEntry.length(); update the same pattern in the
corresponding code paths around the product vulnerability bulk-fetch (the other
branch mentioned at 3048-3097) to ensure both cache hits and misses return the
requested page.
In
`@apps/customer-portal/webapp/src/features/security/components/ProductVulnerabilitiesTable.tsx`:
- Around line 159-161: The effect currently only signals errors to the parent by
calling onError(true) when isError is true, leaving the parent stuck in error
state after recovery; update the effect in the ProductVulnerabilitiesTable
component to call onError with the current isError value (i.e.,
onError(isError)) so the parent is notified both when an error occurs and when
the query recovers (isError becomes false), keeping the dependency array
[isError, onError] intact and guarding the call with onError?.
- Around line 146-153: The paginatedData useMemo can return an empty page when
filteredVulnerabilities shrinks because page isn't clamped; compute total =
filteredVulnerabilities.length and maxPage = Math.max(0, Math.ceil(total /
rowsPerPage) - 1) and ensure page <= maxPage before slicing (if page > maxPage,
call the page state setter to set it to maxPage) — implement this clamp either
inside a useEffect that watches filteredVulnerabilities and rowsPerPage or by
adjusting the logic in the paginatedData useMemo to set the page to maxPage when
needed; reference paginatedData, filteredVulnerabilities, page, rowsPerPage and
your page setter (e.g., setPage) when making the change.
---
Duplicate comments:
In
`@apps/customer-portal/webapp/src/features/security/components/VulnerabilityDetailsContent.tsx`:
- Around line 408-433: The WSO2 Resolution FieldBox is incorrectly bound to
data.resolution and duplicates the Resolution field; update the WSO2 box (label
VULNERABILITY_DETAILS_WSO2_RESOLUTION_LABEL in VulnerabilityDetailsContent) to
bind to the correct property (e.g., data.wso2Resolution or the API-provided
WSO2-specific field) or remove the WSO2 FieldBox if no separate field exists so
it no longer duplicates the FieldBox using data.resolution; ensure the other
Resolution FieldBox remains bound to data.resolution and keep multiline/label
props as-is.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 827c7c59-fd03-4e6c-bb14-937c729777a2
📒 Files selected for processing (11)
apps/customer-portal/backend/Dependencies.tomlapps/customer-portal/backend/modules/entity/types.balapps/customer-portal/backend/modules/types/types.balapps/customer-portal/backend/openapi.yamlapps/customer-portal/backend/service.balapps/customer-portal/webapp/src/features/security/components/ProductVulnerabilitiesList.tsxapps/customer-portal/webapp/src/features/security/components/ProductVulnerabilitiesTable.tsxapps/customer-portal/webapp/src/features/security/components/VulnerabilityDetailsContent.tsxapps/customer-portal/webapp/src/features/security/constants/securityConstants.tsapps/customer-portal/webapp/src/features/security/types/security.tsapps/customer-portal/webapp/src/features/security/utils/productVulnerabilitiesTable.ts
✅ Files skipped from review due to trivial changes (1)
- apps/customer-portal/backend/Dependencies.toml
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/customer-portal/webapp/src/features/security/utils/productVulnerabilitiesTable.ts
- apps/customer-portal/webapp/src/features/security/types/security.ts
- apps/customer-portal/webapp/src/features/security/components/ProductVulnerabilitiesList.tsx
- Expand cache key to include productName, productVersion, and sortBy so distinct filter/sort combinations never share a cache entry - Honor reqOffset/reqLimit when returning bulk-fetch results (both on cache hit and cache miss) so callers requesting a specific page get the correct window of records - Clamp the active page to the last valid page when the filtered dataset shrinks (e.g. after applying a filter), preventing an empty-page render - Pass the actual isError value to onError so the parent state is cleared when the query recovers after a transient failure Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary by CodeRabbit
New Features
Improvements