-
Notifications
You must be signed in to change notification settings - Fork 2
Feat/bindings #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/bindings #10
Conversation
- 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.
✅ Deploy Preview for pushy ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
WalkthroughDependency 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (1 passed)
✨ 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.
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?.messageinto a localmessagevariable 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 commentedappIdfield.The
Bindinginterface includes a commented-outappIdfield. 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
⛔ Files ignored due to path filters (1)
bun.lockis 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:
upsertBindingusesinvalidateQueries(triggering a refetch), whiledeleteBindinguses optimistic cache updates withsetQueriesData. While both approaches are valid, you might consider using optimistic updates forupsertBindingas 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.TabPanechildren to the modernitemsprop 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
itemsarray 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.
| "@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", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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.16Length 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)
- Create a backup branch.
- Update Node to the required version (v4 upgrader requires Node ≥ 20).
- Run: npx @tailwindcss/upgrade (or npx @tailwindcss/upgrade@next for betas). Review diffs. [2][7]
- Update package.json build scripts to v4 CLI/PostCSS plugin as the upgrade tool suggests. [2]
- Search-and-replace removed/renamed utilities (opacity → /50 syntax, spacing/shadow/radius tokens). Use automated tools where possible. [1][4]
- Test across target browsers and component pages; revert or adjust preflight/corePlugins if needed. [1][11]
- 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:
- 1: https://newreleases.io/project/npm/react/release/19.2.0?utm_source=openai
- 2: https://newreleases.io/project/npm/react/release/19.2.0?utm_source=openai
- 3: https://www.wiz.io/vulnerability-database/cve/cve-2025-31137?utm_source=openai
- 4: https://osv.dev/vulnerability/MAL-2025-14589?utm_source=openai
🌐 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:
- 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. - 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). - 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.
| <Layout.Sider | ||
| className="[&>.ant-layout-sider-children]:flex [&>.ant-layout-sider-children]:flex-col!" | ||
| width={240} | ||
| theme="light" | ||
| style={style.sider} | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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.
| 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]); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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!"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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!"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 2Length 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 tsLength 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 -30Length 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.
There was a problem hiding this 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) andpackageMap(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
packageMapalways containsbinding.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.
availablePackagesis set toallPackages, so the dropdown (lines 136-175) shows all packages, including those already bound to this version. While theupsertBindingAPI 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
📒 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)
Summary by CodeRabbit
New Features
Bug Fixes
Refactor
Chores