* feat: AI translations at release, 7 new languages and regional fallback

* fix: proper plural forms for count strings and full translations

* fix: plural forms for selected, hidden and active counts

* fix: synced plugin updates failing on desktop and server-only plugins shown as stopped

* fix: restore rounded corners on app icons and favicon

* feat: desktop app auto updates with automatic, ask first or never modes

* feat: community and custom plugin registries

* feat: plugin guide for AI tools in the SDK with a lint check

* fix: keep the terminal WebSocket open past nginx's 60s default (#1588)

* chore: update changelog

* feat: show plugin download counts only and stop CI builds inflating them

* feat: hide installed plugins in the browse tab by default

* chore: pin bundled plugins to latest releases

* chore: update translations

* fix: focusing a saved password field and saving no longer erases it

* fix: tell desktop users when their 2.8 data needs 2.9 first

* fix: point windows update info at the x64 installer

* fix: keep desktop client tunnel sockets alive behind proxies

* chore: pin bundled plugins to latest releases

* fix: cover client tunnel keepalive in the c2s runtime tests

* fix: typing after opening a host no longer goes to the host search

* fix: accept tab and = separators in SSH config import (#1598)

* chore: update changelog

* chore: drop plugin versions from core changelog

* chore: update translations

* chore: lint, format, and bump version to 26.10.1

---------

Co-authored-by: Darnel Hunter <68697450+innotelinc@users.noreply.github.com>
Co-authored-by: Iain <Iain1970@protonmail.com>
This commit is contained in:
authored and GitHub committed 2026-10-12 11:35:48 -05:00
1 parent 6873bc92bb
commit 62ac931468
174 files changed
+94334 -39995

No files matched your search

+28 -14
View File
@@ -24,7 +24,8 @@ import {
normalizeGitHubRepo,
} from "@/lib/issue-url";
export type PluginStatus = "running" | "disabled" | "failed" | "blocked";
export type PluginStatus =
"running" | "disabled" | "failed" | "blocked" | "server";
/** One row of the Plugins tab: what is installed merged with the registry. */
export interface PluginEntry {
@@ -61,14 +62,17 @@ export interface PluginEntry {
inRegistry: boolean;
contributes: Record<string, unknown> | null;
publishedAt: string | null;
installCount: number | null;
installCountSource: string | null;
downloads: number | null;
source: PluginSource;
/** Installed from a file or folder with no registry signature behind it. */
unverified: boolean;
/** The registry it is listed in or was installed from. */
registryId: string | null;
registryName: string | null;
}
export type PluginSource = "bundled" | "official" | "unverified";
export type PluginSource =
"bundled" | "official" | "community" | "custom" | "unverified";
export function isPrerelease(version: string | null | undefined): boolean {
return Boolean(
@@ -96,10 +100,14 @@ export function pluginSource(summary: PluginSummary): PluginSource {
if (summary.source === "bundled" || summary.tier === "bundled") {
return "bundled";
}
return summary.signedBy ? "official" : "unverified";
if (!summary.signedBy) return "unverified";
return summary.tier === "community" || summary.tier === "custom"
? summary.tier
: "official";
}
export function pluginStatus(summary: PluginSummary): PluginStatus {
if (summary.runsOnServer) return "server";
if (summary.state === "failed") return "failed";
if (summary.state === "blocked") return "blocked";
if (!summary.enabled) return "disabled";
@@ -136,7 +144,10 @@ export function mergePlugins(
latestBeta: entry?.latestBeta ?? null,
channel: summary.channel ?? entry?.channel ?? "stable",
isBeta: isPrerelease(summary.version),
updateAvailable: entry?.updateAvailable ?? false,
// The server keeps its own copy up to date.
updateAvailable: summary.runsOnServer
? false
: (entry?.updateAvailable ?? false),
addedCapabilities: entry?.addedCapabilities ?? [],
versions: entry?.versions ?? [],
capabilities: summary.capabilities ?? [],
@@ -149,10 +160,11 @@ export function mergePlugins(
inRegistry: Boolean(entry),
contributes: (summary.contributes as Record<string, unknown>) ?? null,
publishedAt: latestPublished(entry?.versions ?? []),
installCount: entry?.installCount ?? null,
installCountSource: entry?.installCountSource ?? null,
downloads: entry?.downloads ?? null,
source: pluginSource(summary),
unverified: pluginSource(summary) === "unverified",
registryId: entry?.registryId ?? summary.registryId ?? null,
registryName: entry?.registryName ?? null,
});
}
@@ -191,10 +203,11 @@ export function mergePlugins(
inRegistry: true,
contributes: null,
publishedAt: latestPublished(entry.versions),
installCount: entry.installCount ?? null,
installCountSource: entry.installCountSource ?? null,
source: "official",
downloads: entry.downloads ?? null,
source: entry.tier ?? "official",
unverified: false,
registryId: entry.registryId ?? "official",
registryName: entry.registryName ?? null,
});
}
@@ -230,10 +243,11 @@ export function uploadEntry(preview: PluginUploadPreview): PluginEntry {
inRegistry: false,
contributes: null,
publishedAt: null,
installCount: null,
installCountSource: null,
downloads: null,
source: "unverified",
unverified: true,
registryId: null,
registryName: null,
};
}
@@ -252,7 +266,7 @@ export function sortPlugins(rows: PluginEntry[], key: SortKey): PluginEntry[] {
if (key === "popular") {
sorted.sort(
(a, b) =>
(b.installCount ?? -1) - (a.installCount ?? -1) ||
(b.downloads ?? -1) - (a.downloads ?? -1) ||
a.name.localeCompare(b.name),
);
} else if (key === "updated") {