Transform a single plugin into a "stable, default, indispensable" product capability, and distill reusable migration steps.
Applicable scenarios:
- A certain plugin has become part of the core experience (e.g., TODO, CSV, configuration, change center)
- The old plugin entry point is to be retired, but historical data must be migratable
- Need to avoid registration drift such as "a page missing an import causing an empty plugin list"
Goal definition (align first)
In this project, "fully built-in" is recommended to simultaneously satisfy:
- Stable registration: There is exactly one renderer registrationClient root entry(recommended
SidebarLayout/ShellLayout) - Available by default:
manifest.builtin = true - Not disableable(if true core is needed):
manifest.core = true(registrywill force enable) - Observable and maintainable: Plugin page, settings page, documentation, and tests are consistent
- Historical compatibility: Old entry point data has a migration plan, no content is lost
- Clear surface semantics: Distinguish between "plugins (manageable)" and "in-app built-in capabilities (not shown in the plugin panel)"
Quality gates (mandatory)
Each time this Skill is executed, the following quality perspectives are applied by default (in order):
-
Product design perspective(
product-designer)- Check information hierarchy, task paths, primary/secondary actions, feedback timing
- Ensure "the main path can be completed within 3 steps," avoiding longer flows after migration
-
UI/UX perspective(
ui-design-patterns)- Check button hierarchy, interaction consistency, empty states/error states/loading states
- Prohibit meaningless underlined-link overuse; the visual hierarchy of primary CTA and secondary CTA must be clear
- Dark mode readability must pass (button text contrast, tag contrast, focus visibility)
-
Implementation quality perspective
- Migration logic is idempotent (repeated execution does not duplicate imports)
- Registration mechanism is single-sourced (prohibit multiple scattered imports causing drift)
- Avoid server-side-only registration causing client-side
0/0empty list- Compatibility code failure does not block the main flow (best-effort + observable)
- Verification perspective
- Minimum pass: core/API regression tests + lint
- UI changes require at least one manual check (light/dark)
If any step is not satisfied, the migration must not be judged complete.
Execution flow (general)
Step 1) Inventory the current state and boundaries
First confirm the four types of fact sources for the plugin:
- Code entry:
packages/web/components/renderers/<plugin>/manifest.ts - Registration entry:
packages/web/lib/renderers/index.ts(auto-generated) - Usage entry:
resolveRenderer()call chain (e.g.,ViewPageClient) - Display entry: plugin panel/settings page (prefer using
getPluginRenderers())
And answer:
- Should this plugin be core(not disableable)?
- Is there an old file protocol/old entry point (e.g.,
Agent-Diff.md) that needs migration?
Step 2) Upgrade the plugin declaration
Set in the manifest:
builtin: true(built-in)core: true(if it should be fully built-in and cannot be disabled)appBuiltinFeature: true(if it is an “in-app built-in capability” and should not appear in the plugin management panel)matchKeep it explicit to avoid mismatches
If the plugin is to be replaced/retired:
- Delete the old renderer files
- Regenerate
packages/web/lib/renderers/index.ts
Step 3) Unify the registration mechanism (prevent drift)
It must converge to a single-point initialization (client):
- In the client root component (recommended
SidebarLayout) import@/lib/renderers/index - Remove scattered imports in other pages/components (to avoid duplication and omission coexisting)
Acceptance criteria:
- When entering the plugin page from any route, the plugin list should not show
0/0false empty state - In “in-app built-in capability” mode, the marked capability should disappear from the plugin panel but still render normally
Step 4) Historical compatibility migration (critical)
If the old plugin depends on the old file format:
- Implement “lazy migration” in the core data layer:
- Automatically import the old format on first read
- Write the new format (structured JSON)
- After migration, clean up the old entry files (to prevent dual writes and duplicate imports)
- Provide a one-time script (offline/manual repair):
scripts/migrate-*.js
Step 5) UI/UX alignment
- Plugin page: status and toggleable policies are consistent with core semantics
- Settings page: display builtin/core label logic consistently
- If set to
appBuiltinFeature: consistently hide on the plugin page/settings page/home page Extensions - Complete basic interactions such as filtering, empty state, loading, and read status on the new main process pages
Step 6) Documentation and knowledge synchronization
At minimum synchronize:
wiki/plugins/README.mdwiki/60-stage-plugins.md- Related specs / pitfalls / refs (if the behavior model changes)
And record the migration decision:
- Why move from a plugin entry to a main program capability
- How the old entry remains compatible and when to clean it up
Step 7) Testing and regression
Minimum test set:
- Core layer migration test (old format -> new format)
- API layer filtering/query tests (if there are changes/feed-type interfaces)
- UI basic smoke (plugin list is non-empty, core buttons are usable)
- Category test (
appBuiltinFeaturewhether it can be correctly hidden from the plugin surface)
Recommended command:
npm --prefix app run test -- __tests__/core/content-changes.test.ts __tests__/api/changes.test.ts
AgentDiff -> Built-in reference mapping
This migration can serve as a template:
- Old:
Agent-Diff.md+agent-diffrenderer - New:
.mindos/change-log.json+/api/changes+/changesmain page + global reminder - Compatibility: read the old block and automatically import it; after import, delete
Agent-Diff.md - Governance: delete
diff-viewerrenderer and the old documentation entry, keep the structured audit trail
Common pitfalls checklist
- Only deleting the plugin without migrating old data -> user history is lost
- Registration entries are scattered/registered only on the server side -> the client plugin panel shows
0/0 - Documentation not synchronized -> users see outdated entries
- core/builtin/appBuiltinFeature semantics are confused -> capabilities that should be “in-app built-in” still appear in the plugin panel
- Migration is not idempotent -> Repeatedly importing the same batch of old data on each read
Delivery Checklist (Copyable)
- The manifest layer completes the builtin/core definitions
- If "non-plug-in" is required, add to the manifest
appBuiltinFeature: true - Unify global registration to a single point (imported uniformly at the client root entry)
- The old entry migration logic is implemented and idempotent
- Old files can be cleaned up after migration
- Provide a one-time migration script
- Plugin page/settings page status is correct (including the appBuiltinFeature hiding strategy)
- Documentation has been updated in sync
- Tests pass