Skip to content

Marketplace: support an optional beta build per plugin - #157

Open
DianaSensei wants to merge 1 commit into
mainfrom
plugin-beta-channel
Open

DianaSensei wants to merge 1 commit into
mainfrom
plugin-beta-channel

Conversation

@DianaSensei

Copy link
Copy Markdown
Owner

Summary

  • MarketPlugin gains an optional beta field (MarketPluginVariant: version, pluginManifestUrl, serviceManifestUrl?, targets?), parsed via a shared toMarketPluginVariant/toMarketPlugin in src/lib/market.ts, so a catalog entry can carry a stable release and a beta release side by side.
  • SettingsMarketplace.tsx infers the installed channel by comparing the installed version against the catalog's stable vs. beta version (no schema change to the installed-plugin record), and renders channel-aware install/switch actions:
    • Not installed + has a beta build → Install and Install Beta buttons.
    • Installed on beta → primary action plus Switch to Stable.
    • Installed on stable + has a beta build → primary action plus Switch to Beta.
  • A plugin can only ever have one version installed at a time — switching channels replaces the installed build, it never installs both side by side.
  • Added Vietnamese/English i18n strings for the new install/switch/channel-badge copy.

This is the consumer (devtool app) side of beta-channel support for plugins. The producer side (release scripts emitting/merging a beta catalog entry from a -beta.N tag, plus marking such GitHub releases --prerelease) has been implemented and pushed across all 4 plugin branches in developer-desktop-util-plugin. App-level (DevTool-itself) beta channel support is a separate, deferred follow-up.

Test plan

  • npx tsc --noEmit
  • npx vitest run (1406/1406 passing, including new tests in market.test.ts and SettingsMarketplace.test.tsx)
  • Manually verified regression coverage by temporarily breaking the channel-detection ternary and confirming the relevant test fails

🤖 Generated with Claude Code

https://claude.ai/code/session_01BCypCuViyQDKGWxWspXKs2


Generated by Claude Code

A market plugin can now advertise a beta build alongside its stable one —
same id, same label/description, a separate version + manifest URL(s)
nested under an optional `beta` field. Design constraint from the user:
only ONE version of a given plugin is ever installed at a time, regardless
of channel — picking the other channel replaces whatever's installed, it
never installs a second copy alongside it. So the identity/conflict rules
(assertNoConflictingInstall, findCoupledRecord, recordKey) needed no
changes at all — installing "the beta" is just installing from a different
URL for the same id, exactly like an update already works.

market.ts: MarketPluginVariant factors out the three fields needed to
actually install something (version, pluginManifestUrl, serviceManifestUrl)
— MarketPlugin embeds one directly (the stable build) and optionally a
second under `beta`. toMarketPlugin validates each independently: a
malformed `beta` sub-object only drops the beta option for that plugin, it
doesn't reject the whole catalog entry (same principle already applied to
a malformed entry in the plugins array).

SettingsMarketplace.tsx: MarketPluginCard now infers which channel is
currently installed by comparing the installed version against
plugin.version vs plugin.beta.version (no schema change needed on the
installed-record side — a plugin only ever has one record, whichever
channel it came from). Not installed + a beta exists → both "Install" and
"Install Beta" show up front. Installed → the primary button still does
its normal update-or-installed thing for whichever channel is running, and
a secondary "Switch to Beta"/"Switch to Stable" button offers the other
channel. installTarget now carries {plugin, channel} instead of just
plugin, so the confirm dialog resolves its URLs from the right variant.

Added a channel badge next to the version (visible only when the plugin
actually has a beta build, so plugins that never published one don't get
a "Stable" label nobody asked for), and switched the version shown to the
plugin's ACTUAL installed version rather than always the catalog's stable
version.

New i18n keys: installBeta, switchToBeta, switchToStable, channelStable,
channelBeta (vi copy intentionally distinct from en — "Stable"/"Beta" as
bare loanwords would trip i18n.test.ts's untranslated-pair check).

Tests: market.test.ts covers parsing/validating the beta field in
isolation; SettingsMarketplace.test.tsx covers the fresh-install two-button
case, installing beta resolves the beta URL (not stable's), and both
switch directions show the right button/hide the wrong one. Verified each
new assertion by temporarily reverting the corresponding logic and
confirming the test fails for the right reason, then restoring it.

This is the plugin/market half of beta-channel support — the app's own
update channel (DevTool itself) is a separate, larger piece of work for
later.
@sonarqubecloud

Copy link
Copy Markdown

@codecov

codecov Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.08108% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 42.71%. Comparing base (c44a211) to head (2c53823).

Files with missing lines Patch % Lines
src/components/SettingsMarketplace.tsx 75.00% 4 Missing and 3 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #157      +/-   ##
==========================================
+ Coverage   42.66%   42.71%   +0.05%     
==========================================
  Files         300      300              
  Lines       19917    19942      +25     
  Branches     4933     4948      +15     
==========================================
+ Hits         8498     8519      +21     
- Misses      10402    10406       +4     
  Partials     1017     1017              
Flag Coverage Δ
frontend 38.00% <81.08%> (+0.06%) ⬆️
rust 65.43% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/lib/i18n.ts 81.81% <ø> (ø)
src/lib/market.ts 92.00% <100.00%> (+0.88%) ⬆️
src/components/SettingsMarketplace.tsx 67.59% <75.00%> (+0.54%) ⬆️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Important

The installed-channel inference misclassifies a beta that is behind the catalog's current beta as stable, producing a contradictory "Stable" badge and a primary Update to {stable} action that is actually a downgrade. Worth fixing before merge.

Reviewed changes

  • MarketPluginVariant + beta field — market.ts factors the installable fields into a shared variant type and parses beta via the same validator, dropping only a malformed beta.
  • Channel-aware actions — SettingsMarketplace.tsx threads a stable | beta channel through installTarget/installUrls, and MarketPluginCard renders Install/Install Beta, Update + Switch to Stable, or Update + Switch to Beta.
  • Channel badge — a Stable/Beta pill next to the installed version, shown only when the catalog advertises a beta.
  • i18n + tests — new EN/VI strings; catalog-parse tests and install/switch UI tests.

ℹ️ Nitpicks

  • MarketPluginCard's unsupported warning is computed from plugin.targets (the stable variant) even when the card is on the beta channel or the user is about to install beta. plugin.beta.targets is parsed in market.ts but never consulted, so a beta built for a narrower target set won't get the card-level warning. The install dialog still reports the real per-manifest target support, so this is limited to the advisory warning.

Important

Pullfrog covered this run's model usage. DeepSeek Flash is fast and cheap — expect lighter work than a frontier model. This model allowance is temporary and separate from your Pullfrog plan. Connect a model-provider subscription or API key →

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (model usage covered by Pullfrog) | 𝕏

// version cũ không còn trong catalog) mặc định 'stable' — đúng giả định gốc
// trước khi có beta: mọi bản cài đều là stable.
const installedChannel: 'stable' | 'beta' | undefined =
installedVersion === undefined ? undefined : installedVersion === plugin.beta?.version ? 'beta' : 'stable';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A beta that is behind the catalog's current beta is classified as stable here. When installedVersion equals neither plugin.version nor the current plugin.beta.version, the card renders a "Stable" badge next to e.g. demo@1.1.0-beta.1, and the primary button becomes Update to {plugin.version} — a downgrade presented as an update. The comment frames this as only affecting versions removed from the catalog, but it is the normal path every time a new -beta.N ships; a test pinning a stale beta would be worth adding.

Technical details
# Installed beta behind the catalog beta is misread as stable

## Affected sites
- `src/components/SettingsMarketplace.tsx:281-282` — `installedChannel` only returns `'beta'` on exact equality with the current `plugin.beta.version`; every other non-stable version falls through to `'stable'`.
- `src/components/SettingsMarketplace.tsx:305-311` — the badge then labels that version `channelStable`.
- `src/components/SettingsMarketplace.tsx:366-369` — the primary action is enabled and labelled `updateTo: plugin.version` (stable), so it replaces the installed beta with the older stable build.

## Required outcome
- An installed prerelease/beta that is not the catalog's latest beta should still be recognised as the beta channel, so the badge, highlight, and primary action (update to `plugin.beta.version`) match the installed build, and switching to stable stays an explicit secondary action.

## Suggested approach (optional)
- Either persist the channel on the installed record (the PR deliberately avoids a Rust schema change), or infer beta-ness from the installed version string's prerelease component rather than exact equality against one catalog value. A version-compare helper is preferable to raw `includes('-')` if one already exists in the repo.

## Open questions for the human (optional)
- Is `plugin.beta` guaranteed to always hold the latest beta (older betas absent from the catalog)? If older betas can appear too, the exact-equality match needs to consider the full set.

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