fix(web): prevent skill card drag from opening a second window#361
fix(web): prevent skill card drag from opening a second window#361
Conversation
On process restart, lastWrittenContent is null, causing the first write() to always hit disk even if the config file already has identical content. This triggers an unnecessary OpenClaw reload on every controller restart. Read the existing file to seed the in-memory cache on the first write() call, so cold starts skip the write when content matches.
Replace the inline hidden file input + button with a polished modal dialog ported from the design-system. Two tabs: Upload Zip (wired to existing zip import backend) and GitHub Link (placeholder, coming soon). Adds Dialog UI component (Radix-based) and i18n keys for EN/ZH-CN. Also fixes controller startup crash (EINVAL) when copying runtime plugin dirs containing node_modules/.bin symlinks by filtering out symlinks during the recursive copy.
…lter Keep main's more targeted approach (dereference: true + skip .bin dirs) instead of the broader all-symlink filter. Both fix the same EINVAL crash but main's approach is more specific and preserves useful symlinks.
The modal state helpers (getSelectedZipFile, createAutoCloseController) were extracted but not committed, breaking the build with TS2307.
The test expected non-.bin symlinks to be preserved as symlinks, but the implementation uses dereference: true which materializes them into real directories. Update the test to assert directory materialization instead of symlink preservation.
Disable native link drag on SkillCard <Link> elements so that dragging a card in Electron no longer triggers a new BrowserWindow.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a skill import feature (modal UI, file validation, auto-close controller, dialog primitives, i18n) plus tests for import-state logic and OpenClawRuntimePluginWriter symlink handling; integrates the modal into the Skills page and removes the prior inline file-upload flow. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
apps/controller/tests/openclaw-runtime-plugin-writer.test.ts (2)
29-31: Guard teardown to avoid masking setup failures.If Line 22 throws before assignment, Line 30 can throw during cleanup and obscure the real failure. A small guard makes the failure signal cleaner.
🛠️ Proposed hardening
-let rootDir: string; +let rootDir: string | undefined; @@ afterEach(async () => { - await rm(rootDir, { recursive: true, force: true }); + if (rootDir) { + await rm(rootDir, { recursive: true, force: true }); + rootDir = undefined; + } });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/controller/tests/openclaw-runtime-plugin-writer.test.ts` around lines 29 - 31, The teardown in the afterEach block can throw if rootDir was never assigned during setup; modify the afterEach to guard the cleanup by checking that rootDir is defined and exists before calling rm (e.g., typeof rootDir !== 'undefined' and/or fs.existsSync(rootDir)), then await rm only when the guard passes; update the afterEach containing rm(...) so it references rootDir and rm safely to avoid masking setup failures.
23-26: Narrow the env contract instead of force-casting toControllerEnv.Line 23–26 uses
as ControllerEnv, which hides the actual interface contract. The class only accessesruntimePluginTemplatesDirandopenclawExtensionsDir, so the constructor should declare a minimal interface rather than relying on a broad type assertion.Define a
RuntimePluginWriterEnvinterface in the writer, update the constructor parameter, and remove the type assertion in the test. This prevents contract drift and keeps type checks strict without deferred failures.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/controller/tests/openclaw-runtime-plugin-writer.test.ts` around lines 23 - 26, Define a narrow interface named RuntimePluginWriterEnv in the openclaw runtime writer module that exposes only runtimePluginTemplatesDir and openclawExtensionsDir, change the writer class constructor signature to accept RuntimePluginWriterEnv instead of ControllerEnv (e.g., constructor(env: RuntimePluginWriterEnv) or update the class name's constructor accordingly), and then remove the test's force-cast "as ControllerEnv" in apps/controller/tests/openclaw-runtime-plugin-writer.test.ts so the test object matches the new RuntimePluginWriterEnv shape directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/components/skills/import-skill-modal.tsx`:
- Around line 189-197: The fallback hardcoded message "Import failed" in the
import-skill-modal UI should be replaced with a localized string; update the JSX
where importMutation.error?.message is used (inside the component in
import-skill-modal.tsx) to call the app's i18n/translation helper (e.g.,
t('skills.importFailed') or the project's equivalent) so the fallback uses a
translated message instead of the hardcoded English text.
---
Nitpick comments:
In `@apps/controller/tests/openclaw-runtime-plugin-writer.test.ts`:
- Around line 29-31: The teardown in the afterEach block can throw if rootDir
was never assigned during setup; modify the afterEach to guard the cleanup by
checking that rootDir is defined and exists before calling rm (e.g., typeof
rootDir !== 'undefined' and/or fs.existsSync(rootDir)), then await rm only when
the guard passes; update the afterEach containing rm(...) so it references
rootDir and rm safely to avoid masking setup failures.
- Around line 23-26: Define a narrow interface named RuntimePluginWriterEnv in
the openclaw runtime writer module that exposes only runtimePluginTemplatesDir
and openclawExtensionsDir, change the writer class constructor signature to
accept RuntimePluginWriterEnv instead of ControllerEnv (e.g., constructor(env:
RuntimePluginWriterEnv) or update the class name's constructor accordingly), and
then remove the test's force-cast "as ControllerEnv" in
apps/controller/tests/openclaw-runtime-plugin-writer.test.ts so the test object
matches the new RuntimePluginWriterEnv shape directly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2b8848f9-209e-4de5-acc4-e89f17e5199c
📒 Files selected for processing (8)
apps/controller/tests/openclaw-runtime-plugin-writer.test.tsapps/web/src/components/skills/import-skill-modal-state.tsapps/web/src/components/skills/import-skill-modal.tsxapps/web/src/components/ui/dialog.tsxapps/web/src/i18n/locales/en.tsapps/web/src/i18n/locales/zh-CN.tsapps/web/src/pages/skills.tsxapps/web/tests/import-skill-modal-state.test.ts
The "All" sub-tab count was derived from yourSkillsList which is filtered by the active sub-tab, so switching to "Installed" made the "All" count drop to 0. Use installedSkills.length instead.
|
/cr |
|
✅ CR topic created in Feishu topic group Refly CR. |
Summary
<Link>rendered as a native<a>tag with default drag behaviordraggable={false}to theSkillCard<Link>component to suppress the browser's built-in link-drag navigationTest plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
UI Components
Localization
Tests