feat(tui): redesign /plugins as a tabbed panel#1025
Conversation
Split the /plugins manager into Installed / Official / Third-party / Custom tabs. The Official and Third-party marketplace catalogs load lazily, so /plugins opens instantly and keeps working offline, with fetch failures shown inline instead of closing the panel. The tab strip is shared with the /model provider tabs via the new renderTabStrip helper.
🦋 Changeset detectedLatest commit: 5b50532 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5e218d24da
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| return this.marketplaceEntries.filter((entry) => entry.tier === 'official'); | ||
| } | ||
|
|
||
| private get thirdPartyEntries(): readonly PluginMarketplaceEntry[] { |
There was a problem hiding this comment.
Show untiered marketplace entries in a tab
When a custom marketplace entry omits tier (which the parser accepts and the documented example does), it is excluded from both marketplace tabs because Official filters only official and Third-party filters only curated. In that scenario /plugins marketplace <source> successfully loads the JSON but both tabs show no installable plugins, so users cannot install otherwise valid custom marketplace items from the panel.
Useful? React with 👍 / 👎.
| case 'not-installed': | ||
| return entry.version === undefined ? 'install' : `install v${entry.version}`; | ||
| } | ||
| return installedIds.has(entry.id) ? 'installed' : installStatus(entry); |
There was a problem hiding this comment.
Preserve update status for installed entries
For an installed plugin whose marketplace entry has a newer semver version, this now always renders installed because only the id set is passed into the status calculation. The previous marketplace UI used the installed version map and computeUpdateStatus, so users could see update <local> → <latest>; after this change an outdated plugin is indistinguishable from an up-to-date one even though pressing Enter would update it.
Useful? React with 👍 / 👎.
Address Codex review feedback on the /plugins tab redesign: - Untiered marketplace entries (no `tier` field) now appear on the Third-party tab instead of being invisible in both marketplace tabs. - Installed plugins whose marketplace version is newer than the local version render an `update <local> → <latest>` badge again, and up-to-date plugins show `installed · v<version>` — restoring the update visibility the pre-redesign marketplace UI had.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7523767ae6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| } | ||
| const plugin = plugins[this.selectedIndex]; | ||
| const ch = printableChar(data); | ||
| if (matchesKey(data, Key.space)) { |
There was a problem hiding this comment.
Decode Space for installed-plugin toggle
In terminals that send printable keys via Kitty/CSI-u sequences (for example VS Code’s integrated terminal), the Space key needs the decoded printableChar(data) === ' ' path; this handler computes ch but only checks matchesKey(data, Key.space). As a result the documented Installed-tab Space shortcut silently stops toggling plugins in those terminals, unlike the MCP selector and other dialogs that handle both forms.
Useful? React with 👍 / 👎.
In terminals that send printable keys via Kitty/CSI-u sequences (e.g. VS Code's integrated terminal), the Space key arrives as a printable char rather than a Key.space match, so the Installed-tab Space toggle silently stopped working. Check both matchesKey(Key.space) and the decoded printable char to match the MCP selector and other dialogs.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 652505cf81
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (sub === 'marketplace') { | ||
| await showPluginMarketplacePicker(host, rest.join(' ').trim() || undefined); | ||
| await showPluginsPicker(host, { | ||
| initialTab: 'official', |
There was a problem hiding this comment.
Open custom marketplaces on a tab that contains their entries
When /plugins marketplace <source> points at a custom catalog whose entries omit tier, this always opens the Official tab even though those untiered entries are classified into the Third-party tab (entry.tier !== 'official'). The lazy load then leaves the visible tab saying no plugins were found and Enter cannot install anything, whereas the old marketplace picker showed all entries from the supplied source; this affects existing/custom marketplace JSON that does not add tier.
Useful? React with 👍 / 👎.
When `/plugins marketplace <source>` points at a custom catalog whose entries omit `tier`, those entries are classified into the Third-party tab. Opening on Official left the visible tab empty and Enter could not install anything, unlike the old marketplace picker which showed all entries from the supplied source. Open on Third-party when a custom source is supplied; the default catalog still lands on Official.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f85eb10a60
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| | `D` | Remove the selected installed plugin (Installed tab) | | ||
| | `M` | Manage MCP servers for the selected plugin (Installed tab) | | ||
| | `R` | Reload `installed.json` and all manifests (Installed tab) | | ||
| | `Enter` | Installed tab: view plugin details · Official/Third-party tab: open or install · Custom tab: install | |
There was a problem hiding this comment.
Remove the unsupported marketplace open action
The new docs say Enter on Official/Third-party can “open or install” marketplace rows (and the changeset advertises setup URLs), but the implementation only emits install selections and always calls installPluginFromSource(selection.entry.source). In these tabs a row either installs/updates, or the catalog fails if source is missing, so this should be documented as install/update only unless URL-opening rows are actually implemented.
Useful? React with 👍 / 👎.
| ## Installation and Management | ||
|
|
||
| Run `/plugins` in the TUI to open the plugin manager, where you can perform all routine operations. Common keys: | ||
| Run `/plugins` in the TUI to open the plugin manager. It is a single panel with four tabs — **Installed** (manage what you have), **Official** (Kimi-maintained marketplace plugins), **Third-party** (marketplace plugins from other publishers), and **Custom** (install from a URL) — switched with `Tab` / `Shift+Tab`. Common keys: |
There was a problem hiding this comment.
docs/AGENTS.md says keyboard shortcuts use hyphens such as Shift-Tab, not plus signs; this added paragraph/table uses Shift+Tab (also mirrored in docs/zh/customization/plugins.md). Please use Shift-Tab so the new docs follow the repository's documented typography.
Useful? React with 👍 / 👎.
Address Codex review feedback: - The marketplace Enter action is install/update only (open-url rows were removed), so say "install or update" instead of "open or install" and drop the leftover changeset sentence about setup URLs. - Use `Shift-Tab` (hyphen) instead of `Shift+Tab` to match the docs typography convention.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9f4f72d371
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (matchesKey(data, Key.down)) { | ||
| this.selectedIndex = Math.min(entries.length - 1, this.selectedIndex + 1); |
There was a problem hiding this comment.
Keep marketplace selection non-negative while loading
When the Official/Third-party catalog is still loading, entries is empty, so pressing ↓ stores selectedIndex = -1. If the user hits ↓ while waiting for the lazy marketplace fetch, the later loaded list has no selected row and Enter reads entries[-1], making the first install attempt silently do nothing until another navigation key resets the index. Ignore navigation while there are no entries or clamp the lower bound back to 0.
Useful? React with 👍 / 👎.
| const totalSegmentWidth = segments.reduce((sum, s) => sum + visibleWidth(s), 0); | ||
| if (1 + totalSegmentWidth <= width) { | ||
| return ' ' + segments.join(' '); |
There was a problem hiding this comment.
Count separator spaces before declaring tabs fit
This fit check only sums the tab cell widths, but the returned string also inserts spaces via segments.join(' '). For the new /plugins tab strip, terminal widths around 43–45 columns are treated as fitting even though the rendered line is wider, so the parent truncates the right edge instead of showing the </> scroll markers and can hide the Custom tab. Include the inter-tab separator widths in the fit calculation.
Useful? React with 👍 / 👎.
When the Official/Third-party catalog is still loading, `entries` is empty and pressing ↓ computed `Math.min(-1, selectedIndex + 1)` = -1. The later Enter then read `entries[-1]` and the first install silently did nothing. Clamp the index to 0 while there are no entries.
renderTabStrip declared a strip to fit whenever the sum of tab cell widths
fit, but the returned string also inserts single spaces between tabs via
`segments.join(' ')`. At widths around 43-45 columns for a four-tab strip
this declared a fit while the joined line was wider, so the trailing tab
got truncated instead of showing the `<`/`>` scroll markers. Count the
inter-tab separators in both the full-fit check and the scrolling window
fit check.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3b7867adc0
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| 2. Find **Kimi Datasource** and press `Enter` to install | ||
| 3. Run `/reload` or `/new` after installation | ||
|
|
||
| For data capabilities and usage examples, see the [Official Plugins documentation](./datasource.md). |
There was a problem hiding this comment.
Fix the Kimi Datasource docs link
This link now sends readers to docs/en/customization/datasource.md, but that page is only a redirect back to ./plugins.html#kimi-datasource; this commit removed the ## Kimi Datasource section/anchor from plugins.md, so the "data capabilities and usage examples" path lands on a missing anchor instead of the promised documentation. The zh mirror has the same redirect, so either keep the anchor/content here or update the datasource redirect/page before pointing users to it.
Useful? React with 👍 / 👎.
The datasource.md redirect pointed at ./plugins.html#kimi-datasource, but plugins.md no longer has a `## Kimi Datasource` heading — it is now `## Official Plugins`. Update the en/zh redirect targets and fallback links to #official-plugins / #官方插件 so the link lands on an existing anchor.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1b0bdffa5e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| - - meta | ||
| - http-equiv: refresh | ||
| content: 0; url=./plugins.html#kimi-datasource | ||
| content: 0; url=./plugins.html#official-plugins |
There was a problem hiding this comment.
Preserve the Kimi Datasource details page
Because plugins.md now points readers to ./datasource.md for Datasource capabilities and usage examples while this page immediately redirects back to plugins.html#official-plugins, the docs form a loop and the removed Datasource capabilities, coverage, billing, and usage notes are no longer reachable; the zh mirror has the same pattern, so users looking for the official plugin details lose that content.
Useful? React with 👍 / 👎.
The `## Official Plugins` section had replaced the original `## Kimi Datasource` section, leaving the datasource.md redirect pointing at a missing anchor and the Datasource capabilities/usage unreachable. Restore a concise `## Kimi Datasource` section (intro + OAuth login + install steps + usage) in both en and zh so the #kimi-datasource anchor is valid again and the content is reachable.
The tab-redesign rewrite had dropped the `### Installing from GitHub` / `### 从 GitHub 安装` subheading and its lead sentence, leaving only the four URL forms. Restore the heading and lead sentence in both en and zh.
- Condense the Official / Third-party / Custom tab overview and trust-badge note - Trim the custom marketplace JSON section to the minimal id + source shape - Move and expand the Kimi Datasource section with install, usage, and coverage
- Use sentence case for the Datasource headings (How to use, What you can do) - Rename the Datasource caveat heading to Billing and limitations / 计费与限制 to avoid a duplicate Notes / 注意事项 anchor - Remove the Next steps section, which linked back to the on-page Datasource anchor
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6715774ddf
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| } | ||
|
|
||
| override render(width: number): string[] { | ||
| const { colors } = this.opts; |
There was a problem hiding this comment.
Read the dialog palette at render time
In auto-theme mode, KimiTUI.applyResolvedAutoTheme updates currentTheme and requests a render without remounting the open editor replacement. Because this render path uses the colors object captured in PluginsPanelOptions (passed as host.state.theme.palette), /plugins can stay painted with the old palette until the panel is closed, which violates apps/kimi-code/AGENTS.md’s rule that “Theme switching must take effect within a single render”; read currentTheme.palette during render instead of storing a palette snapshot.
Useful? React with 👍 / 👎.
The /plugins panel and MCP selector captured a palette snapshot at construction. In auto theme mode, applyResolvedAutoTheme swaps currentTheme.palette and re-renders without remounting the open panel, so it kept stale colors until closed. Read currentTheme.palette during render instead, drop the colors opt from both components and their call sites, and add a regression test that switches palettes on a mounted panel.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f177414b18
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| labels: this.tabs.map((tab) => tab.label), | ||
| activeIndex: this.activeIndex, | ||
| width, | ||
| colors: this.opts.colors, |
There was a problem hiding this comment.
Read the current palette when rendering model tabs
When /model or the provider flow is left open while auto theme detection swaps currentTheme.palette, the inner model selector repaints from currentTheme but this tab strip keeps using the palette captured in opts.colors at construction, so the tabs stay in the old theme until the dialog is closed and reopened. This also conflicts with apps/kimi-code/AGENTS.md Color Rules, which require theme switching to take effect within a single render; pass currentTheme.palette on the render path instead of storing a snapshot.
Useful? React with 👍 / 👎.
TabbedModelSelectorComponent cached a palette snapshot in opts and used it only for the tab strip. In auto theme mode the inner model list repaints from currentTheme but the strip kept the old colors until the dialog was closed. Read currentTheme.palette on the render path instead, drop the colors opt and its three call sites, and add a regression test that switches palettes on a mounted selector and asserts the strip repaints. This removes the last palette snapshot among editor-replacement dialogs.
|
Codex Review: Didn't find any major issues. You're on a roll. Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
Related Issue
N/A — no prior issue; this is a UX refinement of the
/pluginsmanager.Problem
The
/pluginsmanager previously presented the installed list, the marketplace catalog, and the custom install flow in a less organized way, and opened the marketplace eagerly even when the user only wanted to manage installed plugins.What changed
Redesign
/pluginsas a single tabbed panel:The Official and Third-party catalogs load lazily, so
/pluginsopens instantly and keeps working offline; a marketplace fetch failure is shown inline instead of closing the panel. The tab strip is shared with the/modelprovider tabs via the newrenderTabStriphelper.Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.