- {snapshot.recentActions.length ? (
- <>
-
-
- {t("plugins.toolbar.recent")}
-
- {snapshot.recentActions.map((action) => renderAction(action, "recent", pluginNames.get(action.pluginId)))}
-
- >
- ) : null}
-
{groups.length > 0 ? groups.map((group, index) => (
{index > 0 ? : null}
{group.name}
{group.actions.map((action) => renderAction(action, group.pluginId))}
- {group.hasSettings ? (
- setOpen(false)}>
-
-
- {t("plugins.settings.title")}
-
-
- ) : null}
)) : (
{t("plugins.toolbar.empty")}
diff --git a/apps/web/src/lib/plugins/plugin-host.test.mjs b/apps/web/src/lib/plugins/plugin-host.test.mjs
index 65b5a774..e44a1d1d 100644
--- a/apps/web/src/lib/plugins/plugin-host.test.mjs
+++ b/apps/web/src/lib/plugins/plugin-host.test.mjs
@@ -227,7 +227,6 @@ describe("EdgeEverPluginHost", () => {
expect(host.getSnapshot().commands).toHaveLength(7);
expect(host.getSnapshot().panels).toEqual([{ pluginId: "org.edgeever.test-plugin", id: "fixture", title: "Fixture panel", presentation: "dialog" }]);
expect(notices).toEqual(["hello from plugin"]);
- expect(host.getSnapshot().recentActions[0]).toMatchObject({ id: "hello", type: "command" });
await host.runCommand("org.edgeever.test-plugin", "read-without-permission");
await host.runCommand("org.edgeever.test-plugin", "subscribe-without-read-permission");
await host.runCommand("org.edgeever.test-plugin", "replace-selection");
@@ -245,7 +244,6 @@ describe("EdgeEverPluginHost", () => {
const container = {};
const disposePanel = await host.mountPanel("org.edgeever.test-plugin", "fixture", container);
expect(container.mountedByFixture).toBe(true);
- expect(host.getSnapshot().recentActions[0]).toMatchObject({ id: "fixture", type: "panel" });
disposePanel();
expect(container.mountedByFixture).toBe(false);
const mountedDuringDisable = {};
@@ -253,7 +251,6 @@ describe("EdgeEverPluginHost", () => {
await host.setEnabled("org.edgeever.test-plugin", false);
expect(mountedDuringDisable.mountedByFixture).toBe(false);
expect(host.getSnapshot().panels).toHaveLength(0);
- expect(host.getSnapshot().recentActions).toHaveLength(0);
await host.uninstall("org.edgeever.test-plugin");
expect(secrets.has("test:org.edgeever.test-plugin:token")).toBe(false);
expect(secrets.has("test:org.edgeever.test-plugin:setting:token")).toBe(false);
diff --git a/apps/web/src/lib/plugins/plugin-host.ts b/apps/web/src/lib/plugins/plugin-host.ts
index 50f518ca..479b042d 100644
--- a/apps/web/src/lib/plugins/plugin-host.ts
+++ b/apps/web/src/lib/plugins/plugin-host.ts
@@ -43,7 +43,6 @@ const INSTALLED_EXTENSIONS_STORAGE_KEY = "edgeever.extensions.installed.v1";
const ACTIVE_THEME_STORAGE_KEY = "edgeever.extensions.active-theme.v1";
const STORAGE_PREFIX = "edgeever.plugin-data.v1";
const SETTINGS_STORAGE_PREFIX = "edgeever.plugin-settings.v1";
-const RECENT_ACTIONS_STORAGE_PREFIX = "edgeever.extensions.recent-actions.v1";
const readStorageItem = (key: string) => {
try {
@@ -172,7 +171,6 @@ export interface PluginHostSnapshot {
commands: RegisteredPluginCommand[];
panels: RegisteredPluginPanel[];
embeds: RegisteredPluginEmbed[];
- recentActions: RegisteredPluginAction[];
activeThemeId: string | null;
}
@@ -425,8 +423,7 @@ export class EdgeEverPluginHost {
private readonly eventListeners = new Map void }>>();
private extensions = readInstalledExtensions();
private activeThemeId = readStorageItem(ACTIVE_THEME_STORAGE_KEY);
- private snapshot: PluginHostSnapshot = { extensions: [], commands: [], panels: [], embeds: [], recentActions: [], activeThemeId: null };
- private recentActions: RegisteredPluginAction[];
+ private snapshot: PluginHostSnapshot = { extensions: [], commands: [], panels: [], embeds: [], activeThemeId: null };
private editorAdapter: PluginEditorAdapter | null = null;
private navigationAdapter: PluginNavigationAdapter | null = null;
private panelAdapter: PluginPanelAdapter | null = null;
@@ -452,7 +449,6 @@ export class EdgeEverPluginHost {
this.secretStorage = options.secretStorage ?? new WebPluginSecretStore();
this.packageStorage = options.packageStorage ?? new WebPluginPackageStore();
this.scheduleAdapter = options.scheduleAdapter;
- this.recentActions = this.readRecentActions();
this.refreshSnapshot();
}
@@ -647,8 +643,6 @@ export class EdgeEverPluginHost {
const extension = this.requireExtension(extensionId);
if (extension.manifest.type === "plugin") await this.deactivatePlugin(extensionId);
this.extensions = this.extensions.filter((item) => item.manifest.id !== extensionId);
- this.recentActions = this.recentActions.filter((action) => action.pluginId !== extensionId);
- this.persistRecentActions();
if (this.activeThemeId === extensionId) {
this.activeThemeId = null;
removeStorageItem(ACTIVE_THEME_STORAGE_KEY);
@@ -715,7 +709,6 @@ export class EdgeEverPluginHost {
const command = this.commands.get(`${pluginId}:${commandId}`);
if (!command) throw new Error("Plugin command is not registered.");
await command.run();
- this.recordRecentAction({ pluginId, id: commandId, title: command.title, type: "command" });
}
async mountPanel(
@@ -749,7 +742,6 @@ export class EdgeEverPluginHost {
if (typeof pluginDispose === "function") pluginDispose();
};
mounted.add(dispose);
- this.recordRecentAction({ pluginId, id: panelId, title: panel.title, type: "panel" });
return dispose;
}
@@ -1517,54 +1509,7 @@ export class EdgeEverPluginHost {
this.refreshSnapshot();
}
- private readRecentActions(): RegisteredPluginAction[] {
- try {
- const parsed = JSON.parse(readStorageItem(`${RECENT_ACTIONS_STORAGE_PREFIX}:${this.scope}`) ?? "[]") as unknown;
- if (!Array.isArray(parsed)) return [];
- return parsed.flatMap((item) => {
- if (!item || typeof item !== "object") return [];
- const action = item as Partial;
- if (
- typeof action.pluginId !== "string" ||
- typeof action.id !== "string" ||
- typeof action.title !== "string" ||
- (action.type !== "command" && action.type !== "panel")
- ) return [];
- return [action as RegisteredPluginAction];
- }).slice(0, 5);
- } catch {
- return [];
- }
- }
-
- private recordRecentAction(action: RegisteredPluginAction) {
- this.recentActions = [
- action,
- ...this.recentActions.filter((item) => item.pluginId !== action.pluginId || item.id !== action.id || item.type !== action.type),
- ].slice(0, 5);
- this.persistRecentActions();
- this.refreshSnapshot();
- }
-
- private persistRecentActions() {
- try {
- writeStorageItem(`${RECENT_ACTIONS_STORAGE_PREFIX}:${this.scope}`, JSON.stringify(this.recentActions));
- } catch {
- // Recent actions are a convenience and must never make a successful plugin action fail.
- }
- }
-
private refreshSnapshot() {
- const registeredActions = new Map([
- ...[...this.commands.values()].map(({ pluginId, id, title }) => [
- `command:${pluginId}:${id}`,
- { pluginId, id, title, type: "command" as const },
- ] as const),
- ...[...this.panels.values()].map(({ pluginId, id, title }) => [
- `panel:${pluginId}:${id}`,
- { pluginId, id, title, type: "panel" as const },
- ] as const),
- ]);
this.snapshot = {
extensions: this.extensions.map((item) => ({ ...item, manifest: { ...item.manifest } })),
commands: [...this.commands.values()].map(({ pluginId, id, title }) => ({ pluginId, id, title })),
@@ -1575,10 +1520,6 @@ export class EdgeEverPluginHost {
presentation: presentation === "fullscreen" ? "fullscreen" : "dialog",
})),
embeds: [...this.embeds.values()].map(({ pluginId, type }) => ({ pluginId, type })),
- recentActions: this.recentActions.flatMap((action) => {
- const registered = registeredActions.get(`${action.type}:${action.pluginId}:${action.id}`);
- return registered ? [registered] : [];
- }),
activeThemeId: this.activeThemeId,
};
for (const listener of this.listeners) listener();
diff --git a/apps/web/src/lib/plugins/plugin-navigation.test.mjs b/apps/web/src/lib/plugins/plugin-navigation.test.mjs
index e7f2f2d5..f86c64e9 100644
--- a/apps/web/src/lib/plugins/plugin-navigation.test.mjs
+++ b/apps/web/src/lib/plugins/plugin-navigation.test.mjs
@@ -17,18 +17,16 @@ describe("plugin settings navigation", () => {
expect(hasPluginSettings({ type: "theme" })).toBe(false);
});
- test("disabled plugins retain settings but never expose executable actions", () => {
+ test("does not expose disabled plugins through the command menu", () => {
const groups = getPluginToolbarGroups({
extensions: [{ manifest: plugin, enabled: false }],
commands: [{ pluginId: "example", id: "run" }],
panels: [{ pluginId: "example", id: "panel" }],
});
- expect(groups).toHaveLength(1);
- expect(groups[0].hasSettings).toBe(true);
- expect(groups[0].actions).toEqual([]);
+ expect(groups).toEqual([]);
});
- test("keeps commands and panels while hiding settings-free inactive plugins and themes", () => {
+ test("keeps commands and panels while hiding settings-only plugins, inactive plugins, and themes", () => {
const groups = getPluginToolbarGroups({
extensions: [
{ manifest: { type: "plugin", id: "actions" }, enabled: true },
@@ -39,11 +37,8 @@ describe("plugin settings navigation", () => {
commands: [{ pluginId: "actions", id: "run" }, { pluginId: "inactive", id: "hidden" }],
panels: [{ pluginId: "actions", id: "panel" }],
});
- expect(groups.map((group) => group.pluginId)).toEqual(["actions", "example"]);
- expect(groups[0].hasSettings).toBe(false);
+ expect(groups.map((group) => group.pluginId)).toEqual(["actions"]);
expect(groups[0].actions.map((action) => action.type)).toEqual(["command", "panel"]);
- expect(groups[1].hasSettings).toBe(true);
- expect(groups[1].actions).toEqual([]);
});
test("resolves deep links and safely falls back for unsupported settings pages", () => {
diff --git a/apps/web/src/lib/plugins/plugin-navigation.ts b/apps/web/src/lib/plugins/plugin-navigation.ts
index 187f87a0..0bca814c 100644
--- a/apps/web/src/lib/plugins/plugin-navigation.ts
+++ b/apps/web/src/lib/plugins/plugin-navigation.ts
@@ -15,12 +15,11 @@ export const getPluginDetailPage = (manifest: ExtensionManifest, requestedPage:
export const getPluginToolbarGroups = (snapshot: Pick) =>
snapshot.extensions.flatMap((extension) => {
if (extension.manifest.type !== "plugin") return [];
- const hasSettings = hasPluginSettings(extension.manifest);
const actions: RegisteredPluginAction[] = extension.enabled ? [
...snapshot.commands.filter((command) => command.pluginId === extension.manifest.id).map((command) => ({ ...command, type: "command" as const })),
...snapshot.panels.filter((panel) => panel.pluginId === extension.manifest.id).map((panel) => ({ ...panel, type: "panel" as const })),
] : [];
- return actions.length || hasSettings
- ? [{ pluginId: extension.manifest.id, name: extension.manifest.name, actions, hasSettings }]
+ return actions.length
+ ? [{ pluginId: extension.manifest.id, name: extension.manifest.name, actions }]
: [];
});
diff --git a/packages/shared/src/i18n/en-US.ts b/packages/shared/src/i18n/en-US.ts
index 4e281445..2ac1fbc2 100644
--- a/packages/shared/src/i18n/en-US.ts
+++ b/packages/shared/src/i18n/en-US.ts
@@ -759,7 +759,6 @@ export const enUS = {
},
toolbar: {
open: "Plugins",
- recent: "Recently used",
empty: "Enabled plugins have not provided any commands or panels yet.",
manage: "Manage plugins and themes",
},
diff --git a/packages/shared/src/i18n/zh-CN.ts b/packages/shared/src/i18n/zh-CN.ts
index 4805cbaf..3f5b3483 100644
--- a/packages/shared/src/i18n/zh-CN.ts
+++ b/packages/shared/src/i18n/zh-CN.ts
@@ -759,7 +759,6 @@ export const zhCN = {
},
toolbar: {
open: "插件",
- recent: "最近使用",
empty: "已启用的插件还没有提供命令或面板。",
manage: "管理插件与主题",
},