Skip to content

Conversation

@sunnylqm
Copy link
Collaborator

@sunnylqm sunnylqm commented Nov 4, 2025

Summary by CodeRabbit

  • New Features

    • Added a publish-compatibility table showing native package support across release modes; publish UI now shows rollout options and clearer publish labels.
  • Bug Fixes

    • Improved error display with clearer "错误:" prefix and a proxy-troubleshooting suggestion.
  • Refactor

    • Sidebar and management pages reorganized for better layout; app items show pause status, counters, and link to details; tabs and breadcrumbs updated to modern APIs.
  • Chores

    • Bumped several dependencies (React 19.2.0, Ant Design 5.28.0, TailwindCSS 4.1.16, etc.).

- Added bindings to the ManageContext to manage package bindings.
- Updated BindPackage component to utilize bindings for rollout configuration.
- Modified types to include packageId in the Binding interface.
- Refactored useBindings hook to streamline binding retrieval.
- Bump @rsbuild/core to version 1.6.1 and @biomejs/biome to version 2.3.3 in package.json and bun.lock.
- Change type definition from 'version' to 'versions' in Package interface for better clarity.
- Update BindPackage component to reference 'versions' for package version checks.
- Modify request service to use localhost for development.
@netlify
Copy link

netlify bot commented Nov 4, 2025

Deploy Preview for pushy ready!

Name Link
🔨 Latest commit d9c7547
🔍 Latest deploy log https://app.netlify.com/projects/pushy/deploys/690a214169e57e000859baf6
😎 Deploy Preview https://deploy-preview-10--pushy.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link

coderabbitai bot commented Nov 4, 2025

Walkthrough

Dependency updates and TypeScript JSX runtime change. Introduced binding APIs (getBinding, upsertBinding, deleteBinding), a useBinding hook, and extended ManageContext to expose bindings and packageMap. Refactored BindPackage to use context, added PublishFeatureTable, adjusted UI components and error handling.

Changes

Cohort / File(s) Summary
Dependency Updates
package.json
Updated multiple dependencies (React 19.1.1→19.2.0, antd 5.27.4→5.28.0, @rsbuild/core, @tanstack/react-query, dayjs, tailwind plugins, etc.).
Type Definitions
src/types.d.ts
Replaced version?/expVersionId? with versions?. Added BindingType and Binding interface (id, type, versionId, packageId, rollout).
Compiler Configuration
tsconfig.json
Switched JSX runtime from "react" to "react-jsx" and removed reactNamespace.
API Layer
src/services/api.ts
Removed bindVersion. Added getBinding(appId), upsertBinding({appId, versionId, packageId, rollout?, config?}), and deleteBinding({appId, bindingId}) with query-cache updates.
Request/Error Handling
src/services/request.ts
Commented/adjusted SERVER.main entries and prefixed non-401 error messages with 错误: plus a proxy suggestion.
Hooks
src/utils/hooks.ts
Added useBinding(appId) returning { bindings, isLoading }. usePackages now memoizes and returns { packages, unusedPackages, packageMap, isLoading }.
Context / State
src/pages/manage/hooks/useManageContext.tsx
Extended ManageContext/defaultManageContext and provider value to include bindings, packageMap, and bindingsLoading.
Binding Component
src/pages/manage/components/bind-package.tsx
Removed packages prop; component now reads bindings and packageMap from context, uses useMemo for bound state, and calls upsertBinding/deleteBinding for changes.
Feature Table
src/pages/manage/components/publish-feature-table.tsx
New PublishFeatureTable component rendering an Ant Design table showing react-native-update version support across release modes with colored Tags and a note.
Version Table
src/pages/manage/components/version-table.tsx
Integrated PublishFeatureTable inside a Popover; updated BindPackage invocation to remove packages prop.
Error Boundary
src/components/error-boundary.tsx
Use local message variable for chunk-loading detection and for subtitle fallback instead of directly accessing error?.message.
Sider / Layout
src/components/sider.tsx
Added exported style constant, wrapped Sider content in a full-height flex column, added overflow wrappers, composite keys for app items, Tooltip counters, and Link-wrapped app entries.
Package List Styling
src/pages/manage/components/package-list.tsx
Minor Tailwind class tweak: [&_li]:!px-0[&_li]:px-0!.
Manage Pages
src/pages/manage/index.tsx
Replaced Tabs/Breadcrumb JSX with items API, adjusted Content className formatting.

Sequence Diagram(s)

sequenceDiagram
    participant UI as UI Component
    participant Context as ManageContext
    participant Hook as useBinding
    participant API as API Service
    participant Cache as Query Cache

    rect rgb(200,220,255)
    Note over UI,Context: Load bindings on mount
    UI->>Context: request bindings
    Context->>Hook: useBinding(appId)
    Hook->>API: getBinding(appId)
    API-->>Hook: bindings[]
    Hook->>Cache: cache bindings
    Hook-->>Context: expose bindings + isLoading
    end

    rect rgb(220,255,220)
    Note over UI,API: Create / Update binding
    UI->>API: upsertBinding({appId, packageId, versionId, rollout?})
    API->>Cache: invalidate /bindings/{appId}
    API-->>UI: success
    end

    rect rgb(255,220,220)
    Note over UI,API: Delete binding
    UI->>API: deleteBinding({appId, bindingId})
    API->>Cache: remove binding from cache
    API-->>UI: success
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Review cache invalidation in new binding APIs and ensure query keys match consumers.
  • Validate ManageContext value propagation and that consumers (BindPackage, other components) use new fields correctly.
  • Verify usePackages memoization dependencies and correct formation of packageMap.
  • Inspect BindPackage refactor for UI state updates and correct use of upsert/delete flows.
  • Check PublishFeatureTable integration for layout/styling and Popover behavior.

Poem

🐰 I hopped through hooks and maps today,

bindings found and props away.
Queries dance and caches clear,
Tables bloom, the UI cheer—
A tiny rabbit codes hooray! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Feat/bindings' is vague and uses generic branch naming convention rather than describing the actual changes; it lacks specificity about what was implemented. Provide a more descriptive title summarizing the main feature, e.g., 'Refactor package binding management to use context and new binding APIs' or similar to convey the core change.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/bindings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (6)
src/components/error-boundary.tsx (1)

13-14: LGTM! Clean refactor to reduce duplication.

Extracting error?.message into a local message variable eliminates repetition and improves readability. The logic remains functionally equivalent across all usages.

Also applies to: 15-19, 39-39

src/services/request.ts (1)

82-83: Consider consolidating error messages for better UX.

Displaying two separate error messages for every non-Unauthorized error may create a poor user experience, and the generic proxy suggestion may not be relevant for all error scenarios (e.g., server errors, validation failures, network timeouts).

Consider consolidating into a single, more informative message:

-      message.error(`错误:${(err as Error).message}`);
-      message.error("如有使用代理,请关闭代理后重试");
+      message.error(
+        `错误:${(err as Error).message}。如网络异常,请检查代理设置后重试。`
+      );

Alternatively, you could conditionally show the proxy suggestion only for network-related errors by checking the error type or message content.

src/pages/manage/components/publish-feature-table.tsx (2)

13-35: Consider separating data structure from display text.

The current implementation mixes display text with logic markers (✓, ✗, ⚠) in the same string fields. This creates tight coupling between the data and render functions, making the code harder to maintain.

Consider using separate status fields:

 dataSource={[
   {
     key: "1",
     version: "< v10.15.0",
-    fullRelease: "✓ 支持",
-    grayRelease: "✗ 不支持",
-    bothRelease: "⚠ 灰度被忽略",
+    fullRelease: "supported",
+    grayRelease: "unsupported",
+    bothRelease: "warning",
   },
   // ... other entries
 ]}

Then adjust the render functions to use these explicit status values instead of string matching.


54-86: Consider extracting the tag rendering logic to reduce duplication.

The render functions for the three columns contain duplicated logic for determining Tag colors based on text content. Extracting this into a helper function would improve maintainability.

Consider creating a helper function:

const renderSupportStatus = (text: string, type: 'full' | 'gray' | 'both') => {
  if (text.includes("✓")) {
    return <Tag color="success"> 支持</Tag>;
  }
  if (type === 'both' && text.includes("⚠")) {
    return <Tag color="warning"> 灰度被忽略</Tag>;
  }
  return <Tag color="error"> 不支持</Tag>;
};

Then use it in the column definitions:

render: (text: string) => renderSupportStatus(text, 'full')
src/types.d.ts (1)

101-108: Consider removing or documenting the commented appId field.

The Binding interface includes a commented-out appId field. If this field is not needed, it should be removed to avoid confusion. If it might be needed in the future, consider adding a comment explaining why it's currently disabled.

Either remove the commented line or add a descriptive comment:

 interface Binding {
   id: number;
   type: BindingType;
-  // appId: number;
+  // appId: number; // TODO: Consider adding appId for better data integrity in future iterations
   versionId: number;
   packageId: number;
   rollout: number;
 }
src/components/sider.tsx (1)

198-232: Avoid using array index in composite keys.

The key ${i.id}-${index} combines the app ID with the array index. Using the index in keys is an anti-pattern because if the array order changes, React will incorrectly reuse components, potentially causing bugs or unnecessary re-renders.

Since each app has a unique id, use it directly as the key:

-                ...(apps?.map((i, index) => ({
-                  key: `${i.id}-${index}`,
+                ...(apps?.map((i) => ({
+                  key: `apps-${i.id}`,
                   className: "!h-16",

Additionally, fix the Tailwind syntax issue on line 206:

                         <PlatformIcon
                           platform={i.platform}
-                          className="text-xl!"
+                          className="!text-xl"
                         />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b599f5 and 4d5b438.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • package.json (1 hunks)
  • src/components/error-boundary.tsx (2 hunks)
  • src/components/sider.tsx (4 hunks)
  • src/pages/manage/components/bind-package.tsx (3 hunks)
  • src/pages/manage/components/package-list.tsx (1 hunks)
  • src/pages/manage/components/publish-feature-table.tsx (1 hunks)
  • src/pages/manage/components/version-table.tsx (3 hunks)
  • src/pages/manage/hooks/useManageContext.tsx (3 hunks)
  • src/pages/manage/index.tsx (2 hunks)
  • src/services/api.ts (1 hunks)
  • src/services/request.ts (2 hunks)
  • src/types.d.ts (2 hunks)
  • src/utils/hooks.ts (3 hunks)
  • tsconfig.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
src/pages/manage/index.tsx (1)
src/components/platform-icon.tsx (1)
  • PlatformIcon (8-25)
src/services/api.ts (2)
src/services/request.ts (1)
  • request (44-87)
src/utils/queryClient.ts (1)
  • queryClient (3-10)
src/utils/hooks.ts (1)
src/services/api.ts (1)
  • api (4-176)
src/pages/manage/components/version-table.tsx (1)
src/pages/manage/components/publish-feature-table.tsx (1)
  • PublishFeatureTable (7-93)
src/pages/manage/hooks/useManageContext.tsx (1)
src/utils/hooks.ts (2)
  • usePackages (54-77)
  • useBinding (113-135)
src/components/sider.tsx (2)
src/router.tsx (1)
  • rootRouterPath (8-18)
src/components/platform-icon.tsx (1)
  • PlatformIcon (8-25)
src/pages/manage/components/bind-package.tsx (2)
src/pages/manage/hooks/useManageContext.tsx (1)
  • useManageContext (29-29)
src/services/api.ts (1)
  • api (4-176)
🔇 Additional comments (7)
src/services/request.ts (1)

15-22: Verify the server URL configuration changes are intentional.

The localhost URL and one production URL (https://5.rnupdate.online/api) are now commented out, leaving only two active endpoints. Please confirm this configuration is correct for your deployment environment and that removing the third production URL won't impact availability or fallback behavior.

tsconfig.json (1)

5-5: LGTM!

The switch to "react-jsx" enables the modern JSX transform, which is the recommended configuration for React 17+ and aligns perfectly with the React 19.2.0 upgrade in this PR.

src/services/api.ts (1)

142-175: LGTM! New binding API methods look solid.

The new binding-focused API surface (getBinding, upsertBinding, deleteBinding) is well-structured and follows the established patterns in this file. The cache invalidation strategy is appropriate for these operations.

Minor note: upsertBinding uses invalidateQueries (triggering a refetch), while deleteBinding uses optimistic cache updates with setQueriesData. While both approaches are valid, you might consider using optimistic updates for upsertBinding as well for better perceived performance, though the current implementation is perfectly acceptable.

src/pages/manage/index.tsx (2)

38-58: LGTM! Ant Design Tabs migration looks good.

The migration from deprecated Tabs.TabPane children to the modern items prop API is correct and aligns with Ant Design 5.x best practices.


83-100: LGTM! Breadcrumb migration is well-implemented.

The migration to the Breadcrumb items array API is clean and maintains all the functionality including the platform icon, app name, and conditional status tag.

src/components/sider.tsx (2)

96-98: LGTM! Good refactor extracting inline styles.

Moving the inline style object to a module-level constant improves code organization and reusability.


138-243: LGTM! Enhanced menu structure with overflow handling.

The refactored menu structure with the overflow container and enhanced app item rendering (including Links and checkCount display) significantly improves the user experience.

Comment on lines +11 to +36
"@ant-design/icons": "^6.1.0",
"@ant-design/v5-patch-for-react-19": "^1.0.3",
"@rsbuild/core": "^1.5.13",
"@rsbuild/core": "^1.6.1",
"@rsbuild/plugin-react": "^1.4.1",
"@rsbuild/plugin-sass": "^1.4.0",
"@rsbuild/plugin-svgr": "^1.2.2",
"@tanstack/react-query": "^5.90.2",
"antd": "^5.27.4",
"dayjs": "^1.11.18",
"@tanstack/react-query": "^5.90.6",
"antd": "^5.28.0",
"dayjs": "^1.11.19",
"git-url-parse": "^16.1.0",
"hash-wasm": "^4.12.0",
"history": "^5.3.0",
"json-diff-kit": "^1.0.33",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router-dom": "^7.9.3",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^7.9.5",
"vanilla-jsoneditor": "^3.10.0"
},
"devDependencies": {
"@biomejs/biome": "2.2.4",
"@tailwindcss/postcss": "^4.1.13",
"@biomejs/biome": "2.3.3",
"@tailwindcss/postcss": "^4.1.16",
"@types/git-url-parse": "^16.0.2",
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/react-router-dom": "^5.3.3",
"tailwindcss": "^4.1.13",
"tailwindcss": "^4.1.16",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify dependency updates for breaking changes and security advisories.

Multiple dependencies have been updated to newer versions. While these appear to be minor/patch updates, it's important to verify that there are no breaking changes or security vulnerabilities, especially for production dependencies like React 19.2.0, Ant Design 5.28.0, and Tailwind CSS 4.1.16.

Run the following script to check for security advisories:


🏁 Script executed:

#!/bin/bash
# Check for npm security advisories across updated dependencies

npm audit --registry=https://registry.npmjs.org/

# Check specific packages for vulnerabilities
echo "Checking React..."
npm view react@19.2.0 

echo "Checking Ant Design..."
npm view antd@5.28.0

echo "Checking Tailwind CSS..."
npm view tailwindcss@4.1.16

Length of output: 3402


Now searching for Tailwind CSS configuration to verify migration requirements:

Verify Tailwind CSS v4 configuration and migration approach before deploying.

Tailwind CSS v4.0 has significant breaking changes, particularly with configuration and CSS imports. The upgrade tool will automate the migration process including updating dependencies, migrating configuration to CSS, and handling template changes, but you should review changes carefully and test in the browser, as complex projects may need manual adjustments.

Critical items to address:

  • Run the upgrade tool in a new branch with Node.js 20+, then review and test changes
  • Tailwind CSS v4 is incompatible with Sass/SCSS files—migrate to plain CSS
  • Check if tailwind.config.js and CSS import statements have been properly migrated per the upgrade tool output
  • Verify browser support requirements (Safari 16.4+, Chrome 111+, Firefox 128+) match your project needs

React 19.2.0 + Ant Design 5.28.0: Ant Design v5 is most features compatible with React 19 with known workarounds. The presence of @ant-design/v5-patch-for-react-19 in your dependencies already addresses compatibility needs. No direct security vulnerabilities detected for antd.


🌐 Web query:

Tailwind CSS 4.1.16 breaking changes migration from v3

💡 Result:

Summary of the important breaking changes when migrating Tailwind CSS v3 → v4 (as of v4.1.16) and how to handle them

  • Use the official upgrade tool first: npx @tailwindcss/upgrade (recommended — it automates most changes). [1][2]
  • Config + packaging
    • Tailwind moves to a CSS-first configuration model (JS config is deprecated by default; you can still keep JS with adapters). Update tailwind.config → new CSS-first syntax or run the upgrader. [1][2]
    • The PostCSS plugin was split: v4 uses @tailwindcss/postcss (and the CLI/watch commands changed). Ensure Node ≥ 20 and update build scripts. [2]
  • How Tailwind is imported
    • @tailwind base/components/utilities directives are replaced by importing the package in a normal CSS file (follow the upgrade tool output). [1][2]
  • Removed / renamed utilities and syntax changes
    • Deprecated utilities from v3 were removed (e.g., the old -opacity- utilities — use color/alpha shorthand like bg-black/50, ring-blue-500/50, etc.). [1]
    • Some utility names/spacing/shadow/radius tokens changed (projects report replacements like space-x/space-y → gap-x/gap-y in some patterns and shadow/rounded token renames). Review the upgrader diff and run full-text searches for old classes. [1][6]
  • Core plugins and preflight behavior
    • Some core plugin behavior and preflight defaults changed (appearance, resize, etc.). You may need to re-enable/disable specific corePlugins in config to restore previous behavior. [1][11]
  • Browser / compatibility tradeoffs
    • v4 adopts more modern CSS features; older browsers may break. If you need older-browser support, consider staying on v3 or add additional polyfills/transpilation. Test target browsers. [2][13]
  • Build/watch issues to watch for
    • New CLI/watch commands and different PostCSS/plugin setup can break existing dev workflows (file-watching, framework integrations). If builds fail after upgrade, create a fresh v4 project to compare configs or re-run the upgrader in a branch. [2][8][12]
  • Migration checklist (practical steps)
    1. Create a backup branch.
    2. Update Node to the required version (v4 upgrader requires Node ≥ 20).
    3. Run: npx @tailwindcss/upgrade (or npx @tailwindcss/upgrade@next for betas). Review diffs. [2][7]
    4. Update package.json build scripts to v4 CLI/PostCSS plugin as the upgrade tool suggests. [2]
    5. Search-and-replace removed/renamed utilities (opacity → /50 syntax, spacing/shadow/radius tokens). Use automated tools where possible. [1][4]
    6. Test across target browsers and component pages; revert or adjust preflight/corePlugins if needed. [1][11]
    7. Fix remaining issues manually and run full test suite / visual checks. [2]

Primary sources and migration docs

  • Tailwind official upgrade guide (v4 changes + migration tool). [1]
  • Tailwind v4 beta/upgrade notes (details on plugin split, CLI changes, migration tool). [2]
  • Community migration notes / examples (common replacements and examples). [4][6]

If you want, I can:

  • run the upgrader on a sample of your project files and show diffs, or
  • produce a tailored search-and-replace map for your codebase (give me a small sample of your tailwind.config.js and representative HTML/JSX).

🌐 Web query:

React 19.2.0 Ant Design 5.28.0 compatibility issues

💡 Result:

Short answer: Ant Design v5 (including 5.28.0) is not fully compatible with React 19 out of the box — common problems are warnings/errors around removed legacy ref/renderer behavior (e.g. "Accessing element.ref was removed") and failures in components that create their own roots (Modal/Notification/Tooltip/Popup). Official/maintainer discussion confirms these issues and there is a community/official patch package you can import as a workaround: @ant-design/v5-patch-for-react-19. [1][2][3][4]

What to do

  • Quick workaround: install/import the patch package (import '@ant-design/v5-patch-for-react-19') which adapts AntD’s internal rendering to React 19. [3][4]
  • If you can’t use the patch, run React 18 (or use --legacy-peer-deps/overrides) or follow Ant Design’s guidance in their issue/discussion threads while waiting for native support. [1][2]

Sources

  • Ant Design GitHub issue & discussion about React 19 incompatibilities. [1][2]
  • Official patch repo: ant-design/v5-patch-for-react-19 (usage: import the package). [3]
  • Community summary / explanation of the patch and why it’s needed. [4]

🌐 Web query:

React 19.2.0 antd security vulnerabilities CVE 2025

💡 Result:

Short answer: I couldn't find any CVE that names React 19.2.0 or the core Ant Design (antd) library itself as vulnerable in 2025. However, several related ecosystem issues were disclosed in 2025 that you should be aware of (React Router, Next.js, malicious/third‑party antd packages). (newreleases.io)

Details (high‑priority items):

  • No public CVE entry listing React 19.2.0 itself as vulnerable in 2025. (React 19.2.0 release info). (newreleases.io)
  • React Router related CVEs in 2025: CVE-2025-31137 (host/X‑Forwarded‑Host spoofing via Express adapter) and CVE-2025-43864 (forcing SPA mode / SSR cache‑poisoning) — these can affect React apps that use those routers/SSR patterns. (wiz.io)
  • Supply‑chain / package issues in the antd ecosystem: a malicious npm package named antd-cloud (MAL-2025-14589) and XSS advisories affecting Ng‑Zorro (Ant Design for Angular) or third‑party antd wrappers — these are not the antd core CVE but impact projects depending on those packages. (osv.dev)

If you want, I can:

  • Look up whether your project (list me your dependencies/versions) is directly impacted and recommend fixes/patch levels; or
  • Produce a short remediation checklist (patch versions, header stripping, cache rules, supply‑chain checks).

Citations:


🌐 Web query:

React Router 7.9.5 CVE-2025-31137 CVE-2025-43864 affected versions

💡 Result:

Short answer: React Router 7.9.5 is NOT affected by CVE-2025-31137 or CVE-2025-43864.

Details & sources:

  • CVE-2025-31137 (Express adapter URL spoofing) was patched in React Router 7.4.1 — versions prior to 7.4.1 were vulnerable. [1]
  • CVE-2025-43864 (SPA-mode / SSR cache‑poisoning DoS) affects >= 7.2.0 and <= 7.5.1 and was patched in 7.5.2. [2][3]
  • Current package metadata shows latest non‑vulnerable React Router is 7.9.5 (i.e., a release newer than the patched versions above). [4]

Sources:
[1] NVD / advisories for CVE-2025-31137.
[2] GitHub Advisory GHSA-f46r-rw29-r322 (CVE-2025-43864).
[3] Snyk security advisory for react-router (CVE-2025-43864).
[4] npm/Snyk package info showing react-router latest version 7.9.5.


🌐 Web query:

Tailwind CSS v4 Sass SCSS support dropped compatibility

💡 Result:

Short answer: Yes — Tailwind CSS v4 officially dropped support for preprocessors (Sass/SCSS, Less, Stylus). The v4 compatibility docs say Tailwind “is not designed to be used with CSS preprocessors” and recommends using Tailwind’s native features / standard CSS (or keep Sass separate and import compiled CSS). [1][2]

If you need next steps: compile your .scss to plain .css and import that CSS (or keep Tailwind in a dedicated .css entry and use your compiled SCSS separately), or migrate styles to native CSS variables/nesting and Tailwind’s APIs. [1][3]

Sources:

  • Tailwind v4 Compatibility docs. [1]
  • Tailwind GitHub issue / discussion re: .scss / Vite and preprocessor compatibility. [2]
  • Community migration notes / guides (examples of compiling SCSS separately). [3]

Address Tailwind CSS v4 breaking changes and Sass plugin incompatibility.

Tailwind CSS v4.1.16 is a major breaking change, not a minor update. Critical issues requiring action:

  1. Sass incompatibility: Tailwind v4 officially dropped Sass/SCSS preprocessor support. The project includes @rsbuild/plugin-sass, which will conflict. Options: (a) remove Sass plugin and migrate styles to plain CSS, or (b) revert to Tailwind v3.
  2. Config migration required: Run npx @tailwindcss/upgrade@next (requires Node ≥ 20) to automate CSS-first config migration, update PostCSS plugin split, and convert import directives. Review diffs carefully, test across target browsers, and adjust deprecated utilities (opacity syntax, spacing/shadow/radius tokens).
  3. Build script updates: Ensure dev/build scripts use the new v4 CLI and PostCSS plugin as the upgrade tool suggests.

React 19.2.0 + Ant Design 5.28.0 compatibility is properly handled—@ant-design/v5-patch-for-react-19 is already in dependencies. React Router 7.9.5 is safe from recent CVEs. No security vulnerabilities detected in core dependencies.

🤖 Prompt for AI Agents
In package.json around lines 11 to 36, Tailwind CSS was bumped to v4.1.16 which
breaks Sass preprocessor support and conflicts with the included
@rsbuild/plugin-sass; fix by either reverting Tailwind to a compatible v3.x
release or removing @rsbuild/plugin-sass and migrating styles to
PostCSS/CSS-first, run npx @tailwindcss/upgrade@next (requires Node ≥ 20) to
convert config/imports and split the PostCSS plugin as the tool suggests, update
dev/build scripts to use the new Tailwind v4 CLI/PostCSS plugin, and then run
full cross-browser tests and adjust deprecated utility tokens
(opacity/spacing/shadow/radius) as needed before committing.

Comment on lines +115 to +120
<Layout.Sider
className="[&>.ant-layout-sider-children]:flex [&>.ant-layout-sider-children]:flex-col!"
width={240}
theme="light"
style={style.sider}
>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix the Tailwind CSS important modifier syntax.

The className uses non-standard Tailwind syntax with flex-col! at the end. The important modifier should be a prefix.

Apply this diff:

-    <Layout.Sider
-      className="[&>.ant-layout-sider-children]:flex [&>.ant-layout-sider-children]:flex-col!"
+    <Layout.Sider
+      className="[&>.ant-layout-sider-children]:flex [&>.ant-layout-sider-children]:!flex-col"
       width={240}
       theme="light"
       style={style.sider}
     >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Layout.Sider
className="[&>.ant-layout-sider-children]:flex [&>.ant-layout-sider-children]:flex-col!"
width={240}
theme="light"
style={style.sider}
>
<Layout.Sider
className="[&>.ant-layout-sider-children]:flex [&>.ant-layout-sider-children]:!flex-col"
width={240}
theme="light"
style={style.sider}
>
🤖 Prompt for AI Agents
In src/components/sider.tsx around lines 115 to 120, the Tailwind important
modifier is used as a suffix (`flex-col!`) which is invalid; change the class
group from "[&>.ant-layout-sider-children]:flex
[&>.ant-layout-sider-children]:flex-col!" to use the `!` prefix for the
important modifier — e.g. "[&>.ant-layout-sider-children]:flex
[&>.ant-layout-sider-children]:!flex-col" — so the important modifier is applied
correctly.

Comment on lines 27 to 132
const bindedPackages = useMemo(() => {
const result = [];
const legacyBindings = [];
for (const p of allPackages) {
if (p.versions?.id === versionId) {
const legacyConfig = config?.rollout?.[p.name];
legacyBindings.push({
packageId: p.id,
rollout: legacyConfig,
});
continue;
}
}
const matchedBindings: {
id?: number;
packageId: number;
rollout: number | null | undefined;
}[] = legacyBindings.concat(
bindings.filter((b) => b.versionId === versionId)
);

if (matchedBindings.length === 0 || allPackages.length === 0) return null;

const bindedPackages = packages.map((p) => {
const rolloutConfig = config?.rollout?.[p.name];
const isFull =
rolloutConfig === 100 ||
rolloutConfig === undefined ||
rolloutConfig === null;
const rolloutConfigNumber = Number(rolloutConfig);
const menu: MenuProps = {
items: isFull
for (const binding of matchedBindings) {
const p = packageMap.get(binding.packageId)!;
const rolloutConfig = binding.rollout;
const isFull =
rolloutConfig === 100 ||
rolloutConfig === undefined ||
rolloutConfig === null;
const rolloutConfigNumber = Number(rolloutConfig);
const items: MenuProps["items"] = isFull
? []
: [
{
key: "full",
label: "全量",
icon: <CloudDownloadOutlined />,
onClick: () => {
api.updateVersion({
onClick: () =>
api.upsertBinding({
appId,
packageId: binding.packageId,
versionId,
params: { config: { rollout: { [p.name]: null } } },
});
api.updatePackage({
appId,
packageId: p.id,
params: { versionId },
});
},
}),
},
],
};
];

if (rolloutConfigNumber < 50 && !isFull) {
menu.items!.push({
key: "gray",
label: "灰度",
icon: <ExperimentOutlined />,
onClick: () =>
api.updatePackage({ appId, packageId: p.id, params: { versionId } }),
children: [1, 2, 5, 10, 20, 50]
.filter((percentage) => percentage > rolloutConfigNumber)
.map((percentage) => ({
key: `${percentage}`,
label: `${percentage}%`,
onClick: () =>
api.updateVersion({
appId,
versionId,
params: { config: { rollout: { [p.name]: percentage } } },
}),
})),
if (rolloutConfigNumber < 50 && !isFull) {
items.push({
key: "gray",
label: "灰度",
icon: <ExperimentOutlined />,
children: [1, 2, 5, 10, 20, 50]
.filter((percentage) => percentage > rolloutConfigNumber)
.map((percentage) => ({
key: `${percentage}`,
label: `${percentage}%`,
onClick: () =>
api.upsertBinding({
appId,
packageId: binding.packageId,
versionId,
rollout: percentage,
}),
})),
});
}
if (items.length > 0) {
items.push({ type: "divider" });
}
items.push({
key: "unpublish",
label: "取消绑定",
icon: <RestOutlined />,
onClick: () => {
const bindingId = binding.id;
if (bindingId) {
api.deleteBinding({ appId, bindingId });
} else {
api.updatePackage({
appId,
packageId: p.id,
params: { versionId: null },
});
}
},
});
const button = (
<Button
size="small"
color="primary"
variant={isFull ? "filled" : "dashed"}
>
<span className="font-bold">{p.name}</span>
<span className="text-xs">{isFull ? "" : `(${rolloutConfig}%)`}</span>
</Button>
);
result.push(
<Dropdown key={p.id} menu={{ items }}>
{button}
</Dropdown>
);
}
if (menu.items!.length > 0) {
menu.items!.push({ type: "divider" });
}
menu.items!.push({
key: "unpublish",
label: "取消绑定",
icon: <RestOutlined />,
onClick: () => {
api.updateVersion({
appId,
versionId,
params: { config: { rollout: { [p.name]: null } } },
});
api.updatePackage({
appId,
packageId: p.id,
params: { versionId: null },
});
},
});
const button = (
<Button
size="small"
color="primary"
variant={isFull ? "filled" : "dashed"}
>
<span className="font-bold">{p.name}</span>
<span className="text-xs">{isFull ? "" : `(${rolloutConfig}%)`}</span>
</Button>
);
return (
<Dropdown key={p.id} menu={menu}>
{button}
</Dropdown>
);
});
return result;
}, [allPackages, bindings, versionId]);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix the stale bindings memo.

config (and packageMap) participate in the useMemo computation but are missing from the dependency array, so editing a version’s rollout config won’t refresh the rendered binding buttons until a separate change (like a bindings refetch) happens. Please include every referenced value in the dependency list. For example:

-  }, [allPackages, bindings, versionId]);
+  }, [allPackages, bindings, versionId, config, packageMap]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const bindedPackages = useMemo(() => {
const result = [];
const legacyBindings = [];
for (const p of allPackages) {
if (p.versions?.id === versionId) {
const legacyConfig = config?.rollout?.[p.name];
legacyBindings.push({
packageId: p.id,
rollout: legacyConfig,
});
continue;
}
}
const matchedBindings: {
id?: number;
packageId: number;
rollout: number | null | undefined;
}[] = legacyBindings.concat(
bindings.filter((b) => b.versionId === versionId)
);
if (matchedBindings.length === 0 || allPackages.length === 0) return null;
const bindedPackages = packages.map((p) => {
const rolloutConfig = config?.rollout?.[p.name];
const isFull =
rolloutConfig === 100 ||
rolloutConfig === undefined ||
rolloutConfig === null;
const rolloutConfigNumber = Number(rolloutConfig);
const menu: MenuProps = {
items: isFull
for (const binding of matchedBindings) {
const p = packageMap.get(binding.packageId)!;
const rolloutConfig = binding.rollout;
const isFull =
rolloutConfig === 100 ||
rolloutConfig === undefined ||
rolloutConfig === null;
const rolloutConfigNumber = Number(rolloutConfig);
const items: MenuProps["items"] = isFull
? []
: [
{
key: "full",
label: "全量",
icon: <CloudDownloadOutlined />,
onClick: () => {
api.updateVersion({
onClick: () =>
api.upsertBinding({
appId,
packageId: binding.packageId,
versionId,
params: { config: { rollout: { [p.name]: null } } },
});
api.updatePackage({
appId,
packageId: p.id,
params: { versionId },
});
},
}),
},
],
};
];
if (rolloutConfigNumber < 50 && !isFull) {
menu.items!.push({
key: "gray",
label: "灰度",
icon: <ExperimentOutlined />,
onClick: () =>
api.updatePackage({ appId, packageId: p.id, params: { versionId } }),
children: [1, 2, 5, 10, 20, 50]
.filter((percentage) => percentage > rolloutConfigNumber)
.map((percentage) => ({
key: `${percentage}`,
label: `${percentage}%`,
onClick: () =>
api.updateVersion({
appId,
versionId,
params: { config: { rollout: { [p.name]: percentage } } },
}),
})),
if (rolloutConfigNumber < 50 && !isFull) {
items.push({
key: "gray",
label: "灰度",
icon: <ExperimentOutlined />,
children: [1, 2, 5, 10, 20, 50]
.filter((percentage) => percentage > rolloutConfigNumber)
.map((percentage) => ({
key: `${percentage}`,
label: `${percentage}%`,
onClick: () =>
api.upsertBinding({
appId,
packageId: binding.packageId,
versionId,
rollout: percentage,
}),
})),
});
}
if (items.length > 0) {
items.push({ type: "divider" });
}
items.push({
key: "unpublish",
label: "取消绑定",
icon: <RestOutlined />,
onClick: () => {
const bindingId = binding.id;
if (bindingId) {
api.deleteBinding({ appId, bindingId });
} else {
api.updatePackage({
appId,
packageId: p.id,
params: { versionId: null },
});
}
},
});
const button = (
<Button
size="small"
color="primary"
variant={isFull ? "filled" : "dashed"}
>
<span className="font-bold">{p.name}</span>
<span className="text-xs">{isFull ? "" : `(${rolloutConfig}%)`}</span>
</Button>
);
result.push(
<Dropdown key={p.id} menu={{ items }}>
{button}
</Dropdown>
);
}
if (menu.items!.length > 0) {
menu.items!.push({ type: "divider" });
}
menu.items!.push({
key: "unpublish",
label: "取消绑定",
icon: <RestOutlined />,
onClick: () => {
api.updateVersion({
appId,
versionId,
params: { config: { rollout: { [p.name]: null } } },
});
api.updatePackage({
appId,
packageId: p.id,
params: { versionId: null },
});
},
});
const button = (
<Button
size="small"
color="primary"
variant={isFull ? "filled" : "dashed"}
>
<span className="font-bold">{p.name}</span>
<span className="text-xs">{isFull ? "" : `(${rolloutConfig}%)`}</span>
</Button>
);
return (
<Dropdown key={p.id} menu={menu}>
{button}
</Dropdown>
);
});
return result;
}, [allPackages, bindings, versionId]);
const bindedPackages = useMemo(() => {
const result = [];
const legacyBindings = [];
for (const p of allPackages) {
if (p.versions?.id === versionId) {
const legacyConfig = config?.rollout?.[p.name];
legacyBindings.push({
packageId: p.id,
rollout: legacyConfig,
});
continue;
}
}
const matchedBindings: {
id?: number;
packageId: number;
rollout: number | null | undefined;
}[] = legacyBindings.concat(
bindings.filter((b) => b.versionId === versionId)
);
if (matchedBindings.length === 0 || allPackages.length === 0) return null;
for (const binding of matchedBindings) {
const p = packageMap.get(binding.packageId)!;
const rolloutConfig = binding.rollout;
const isFull =
rolloutConfig === 100 ||
rolloutConfig === undefined ||
rolloutConfig === null;
const rolloutConfigNumber = Number(rolloutConfig);
const items: MenuProps["items"] = isFull
? []
: [
{
key: "full",
label: "全量",
icon: <CloudDownloadOutlined />,
onClick: () =>
api.upsertBinding({
appId,
packageId: binding.packageId,
versionId,
}),
},
];
if (rolloutConfigNumber < 50 && !isFull) {
items.push({
key: "gray",
label: "灰度",
icon: <ExperimentOutlined />,
children: [1, 2, 5, 10, 20, 50]
.filter((percentage) => percentage > rolloutConfigNumber)
.map((percentage) => ({
key: `${percentage}`,
label: `${percentage}%`,
onClick: () =>
api.upsertBinding({
appId,
packageId: binding.packageId,
versionId,
rollout: percentage,
}),
})),
});
}
if (items.length > 0) {
items.push({ type: "divider" });
}
items.push({
key: "unpublish",
label: "取消绑定",
icon: <RestOutlined />,
onClick: () => {
const bindingId = binding.id;
if (bindingId) {
api.deleteBinding({ appId, bindingId });
} else {
api.updatePackage({
appId,
packageId: p.id,
params: { versionId: null },
});
}
},
});
const button = (
<Button
size="small"
color="primary"
variant={isFull ? "filled" : "dashed"}
>
<span className="font-bold">{p.name}</span>
<span className="text-xs">{isFull ? "" : `(${rolloutConfig}%)`}</span>
</Button>
);
result.push(
<Dropdown key={p.id} menu={{ items }}>
{button}
</Dropdown>
);
}
return result;
}, [allPackages, bindings, versionId, config, packageMap]);
🤖 Prompt for AI Agents
In src/pages/manage/components/bind-package.tsx around lines 27 to 132, the
useMemo that builds bindedPackages reads config and packageMap (and also
references api and appId) but only lists [allPackages, bindings, versionId] as
dependencies, causing stale UI when rollout config or packageMap change; fix by
adding the missing dependencies (at minimum config and packageMap) to the
dependency array — and if api or appId can change in the component scope,
include them as well — so the memo recalculates whenever any referenced value
updates.

return (
// const [_, drag] = useDrag(() => ({ item, type: "package" }));
<div className="bg-white my-0 [&_li]:!px-0">
<div className="bg-white my-0 [&_li]:px-0!">
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Verify the Tailwind CSS important modifier syntax.

The important modifier has been moved from !px-0 to px-0!. In standard Tailwind CSS, the important modifier (!) should be a prefix (e.g., !px-0), not a suffix. The syntax px-0! may not be recognized and could fail to apply the intended styling.

Apply this diff to use the standard Tailwind important modifier syntax:

-    <div className="bg-white my-0 [&_li]:px-0!">
+    <div className="bg-white my-0 [&_li]:!px-0">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="bg-white my-0 [&_li]:px-0!">
<div className="bg-white my-0 [&_li]:!px-0">
🤖 Prompt for AI Agents
In src/pages/manage/components/package-list.tsx around line 89, the Tailwind
important modifier is used as a suffix `px-0!` inside the nested selector
(`[&_li]:px-0!`), which is invalid; change it to the correct prefix form by
replacing `[&_li]:px-0!` with `[&_li]:!px-0` so the important flag is applied
properly to the li padding utility.

/>
</Layout.Sider>
<Layout.Content className="!p-0">
<Layout.Content className="p-0!">
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix the Tailwind CSS important modifier syntax.

The important modifier syntax p-0! is non-standard. Tailwind CSS requires the important modifier (!) to be a prefix, not a suffix.

Apply this diff:

-      <Layout.Content className="p-0!">
+      <Layout.Content className="!p-0">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Layout.Content className="p-0!">
<Layout.Content className="!p-0">
🤖 Prompt for AI Agents
In src/pages/manage/index.tsx around line 60, the Tailwind CSS important
modifier is used incorrectly as a suffix `p-0!`; change it to the correct prefix
form `!p-0` on the Layout.Content className so the style becomes
important-compliant (replace `className="p-0!"` with `className="!p-0"`).

hash: string;
version?: Version;
expVersionId?: number;
versions?: Version;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify that the Package interface change doesn't break existing code.

The Package interface appears to have been modified (per the AI summary, version?: Version was replaced with versions?: Version). This is a breaking change that could affect all code referencing package.version.

Run the following script to check for any remaining references to the old field:


🏁 Script executed:

#!/bin/bash
# Search for references to package.version pattern that might be broken

echo "Checking for potential broken references to package.version..."
rg -n --type ts --type tsx 'package\.version[^s]' -A 2 -B 2

echo "Checking for version field access in Package-typed variables..."
ast-grep --pattern $'($PKG as Package).version'

echo "Checking for optional chaining with version..."
rg -n --type ts --type tsx '\.version\?' -A 2 -B 2

Length of output: 229


I'll run corrected verification scripts to properly search the codebase without the unrecognized file type error:


🏁 Script executed:

#!/bin/bash
# Search for references to package.version - using generic type specification
echo "=== Checking for package.version references (removing tsx type) ==="
rg -n 'package\.version[^s]' -A 2 -B 2 --type ts

echo -e "\n=== Checking version field access patterns ==="
rg -n '\.version\s*[:?=]' -A 1 -B 1 --type ts

echo -e "\n=== Searching for Package interface definition ==="
rg -n 'interface Package|type Package' -A 10 --type ts

echo -e "\n=== Checking for versions field (plural) references ==="
rg -n '\.versions' -A 1 -B 1 --type ts

Length of output: 1326



🏁 Script executed:

#!/bin/bash
# Do a comprehensive search for any remaining singular "version" property access
echo "=== Comprehensive search for .version (singular) access patterns ==="
rg -n '\.version\b' --type ts -C 2 | head -50

echo -e "\n=== Search for version property in destructuring or type assertions ==="
rg -n 'version\s*[,}=:]' --type ts -C 1 | grep -i package | head -30

Length of output: 497


Fix incomplete migration of version to versions field in Package interface.

The interface change from version?: Version to versions?: Version introduces a breaking change. Found reference to old field in src/utils/hooks.ts:64 that still accesses p.version:

if (p.version === null) {

This should be updated to use the new versions field.

🤖 Prompt for AI Agents
In src/types.d.ts around line 52 and in src/utils/hooks.ts at line 64, the
Package interface was renamed from `version` to `versions`, but code still
references `p.version`; update that reference to `p.versions` and adjust the
null/undefined check to handle the optional field (e.g., check `p.versions ==
null` or `p.versions === null`/`typeof p.versions === 'undefined'` as
appropriate) so the code uses the new field name and correctly handles missing
values.

- Changed label from "取消绑定" to "取消发布" for unpublish action.
- Updated button text from "绑定" to "发布" to reflect the new context.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/pages/manage/components/bind-package.tsx (2)

27-131: Past review comment still applies: Missing dependencies in useMemo.

The useMemo computation references config (line 32) and packageMap (line 51) but they're not in the dependency array (line 131). This causes stale UI when these values change. As noted in the previous review, add the missing dependencies.


51-51: Add defensive check for packageMap lookup.

The non-null assertion assumes packageMap always contains binding.packageId. If a binding references a deleted package, this will fail at runtime.

Consider adding a safety check:

     for (const binding of matchedBindings) {
-      const p = packageMap.get(binding.packageId)!;
+      const p = packageMap.get(binding.packageId);
+      if (!p) continue;
       const rolloutConfig = binding.rollout;
🧹 Nitpick comments (2)
src/pages/manage/components/publish-feature-table.tsx (1)

13-86: Refactor to separate data from presentation logic.

The render functions (lines 54-86) parse text content to determine which Tag to display. This couples data structure to display logic—if the dataSource strings change, the render logic breaks.

Consider using separate boolean/enum fields in the dataSource:

       dataSource={[
         {
           key: "1",
           version: "< v10.15.0",
-          fullRelease: "✓ 支持",
-          grayRelease: "✗ 不支持",
-          bothRelease: "⚠ 灰度被忽略",
+          fullRelease: true,
+          grayRelease: false,
+          bothRelease: "warning",
         },
         // ... other rows
       ]}
       columns={[
         // ...
         {
           title: "仅全量发布",
           dataIndex: "fullRelease",
           key: "fullRelease",
           align: "center",
-          render: (text: string) => {
-            if (text.includes("✓")) {
-              return <Tag color="success">✓ 支持</Tag>;
-            }
-            return <Tag color="error">✗ 不支持</Tag>;
+          render: (supported: boolean) => {
+            return supported 
+              ? <Tag color="success">✓ 支持</Tag>
+              : <Tag color="error">✗ 不支持</Tag>;
           },
         },
         {
           title: "同时发布",
           dataIndex: "bothRelease",
           key: "bothRelease",
           align: "center",
-          render: (text: string) => {
-            if (text.includes("✓")) {
+          render: (status: boolean | "warning") => {
+            if (status === true) {
               return <Tag color="success">✓ 支持</Tag>;
             }
-            if (text.includes("⚠")) {
+            if (status === "warning") {
               return <Tag color="warning">⚠ 灰度被忽略</Tag>;
             }
             return <Tag color="error">✗ 不支持</Tag>;
           },
         },
       ]}
src/pages/manage/components/bind-package.tsx (1)

25-25: Consider filtering already-bound packages from the available dropdown.

availablePackages is set to allPackages, so the dropdown (lines 136-175) shows all packages, including those already bound to this version. While the upsertBinding API likely handles this, it may confuse users to see the same package in both the bound buttons and the "add" dropdown.

Filter out bound packages:

-  const availablePackages = allPackages;
+  const bindedPackageIds = useMemo(() => {
+    const bindingIds = bindings
+      .filter((b) => b.versionId === versionId)
+      .map((b) => b.packageId);
+    const legacyIds = allPackages
+      .filter((p) => p.versions?.id === versionId)
+      .map((p) => p.id);
+    return new Set([...bindingIds, ...legacyIds]);
+  }, [allPackages, bindings, versionId]);
+  
+  const availablePackages = allPackages.filter(
+    (p) => !bindedPackageIds.has(p.id)
+  );

Also applies to: 136-175

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d5b438 and d9c7547.

📒 Files selected for processing (2)
  • src/pages/manage/components/bind-package.tsx (4 hunks)
  • src/pages/manage/components/publish-feature-table.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/pages/manage/components/bind-package.tsx (2)
src/pages/manage/hooks/useManageContext.tsx (1)
  • useManageContext (29-29)
src/services/api.ts (1)
  • api (4-176)

@sunnylqm sunnylqm merged commit 7ac1f9b into main Nov 5, 2025
5 checks passed
@sunnylqm sunnylqm deleted the feat/bindings branch November 5, 2025 02:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants