fix: let the repair screen turn the layout plugin back on

This commit is contained in:
LukeGus committed 2026-10-21 11:55:50 -05:00
1 parent 28cb1bc554
commit 53c4e5fe9c
5 files changed
+59 -2

No files matched your search

+2
View File
@@ -1098,6 +1098,8 @@
"resetEveryone": "Reset the default for everyone",
"update": "Update {{name}} to {{version}}",
"disable": "Turn off {{name}}",
"enable": "Turn on {{name}}",
"startAgain": "Start {{name}} again",
"install": "Find layouts and themes",
"installTitle": "Layouts and themes",
"installButton": "Install",
+12
View File
@@ -81,6 +81,18 @@ function AdminTools({
})}
</Button>
)}
{entry?.installed && !entry.enabled && (
<Button
size="sm"
onClick={() => void manager.toggle(entry).then(onRetry)}
disabled={manager.busy.has(entry.id)}
>
<Power />
{status.reason === "disabled"
? t("looks.repair.enable", { name: entry.name })
: t("looks.repair.startAgain", { name: entry.name })}
</Button>
)}
{entry?.installed && entry.enabled && !entry.managedByServer && (
<Button
size="sm"
+3 -1
View File
@@ -79,13 +79,15 @@ export function layoutStatus(input: LayoutStatusInput): LayoutStatus {
const owner = input.owner;
if (!owner) return repair("missing");
const summary = owner.summary;
if (!summary.enabled) return repair("disabled");
// Before the enabled check: the server reports a plugin that failed to
// start as not enabled, which hid the real error behind "turned off".
if (owner.frontend === "blocked" || summary.state === "blocked") {
return repair("blocked", { error: owner.error ?? summary.lastError });
}
if (owner.frontend === "failed" || summary.state === "failed") {
return repair("failed", { error: owner.error ?? summary.lastError });
}
if (!summary.enabled) return repair("disabled");
if (owner.frontend === "loading") return { kind: "loading" };
// Just chosen: the runtime is about to load its code.
if (
+29 -1
View File
@@ -53,6 +53,8 @@ vi.mock("@/look/use-look-theme", () => ({
useChooseTheme: () => look.chooseTheme,
}));
const fancy = vi.hoisted(() => ({ enabled: true }));
const manager = vi.hoisted(() => ({
requestUpdate: vi.fn(),
requestInstall: vi.fn(),
@@ -72,7 +74,7 @@ vi.mock("@/plugins/use-plugins-manager", () => ({
id: "fancy-layout",
name: "Fancy",
installed: true,
enabled: true,
enabled: fancy.enabled,
updateAvailable: true,
latestVersion: "1.1.0",
managedByServer: false,
@@ -115,6 +117,7 @@ const onRetry = vi.fn();
beforeEach(() => {
permissions.isAdmin = true;
fancy.enabled = true;
for (const fn of [
...Object.values(look),
...Object.values(manager),
@@ -190,6 +193,31 @@ describe("RepairView", () => {
);
});
it("lets an admin turn the plugin back on and then tries again", async () => {
fancy.enabled = false;
manager.toggle.mockResolvedValue(undefined);
render(
<RepairView
status={{ ...status, reason: "disabled", error: null }}
onRetry={onRetry}
/>,
);
expect(screen.queryByText(/looks\.repair\.disable/)).toBeNull();
fireEvent.click(screen.getByText('looks.repair.enable:{"name":"Fancy"}'));
expect(manager.toggle).toHaveBeenCalledWith(
expect.objectContaining({ id: "fancy-layout", enabled: false }),
);
await waitFor(() => expect(onRetry).toHaveBeenCalled());
});
it("offers to start a failed plugin again", () => {
fancy.enabled = false;
render(<RepairView status={status} onRetry={onRetry} />);
expect(
screen.getByText('looks.repair.startAgain:{"name":"Fancy"}'),
).toBeTruthy();
});
it("lets an admin restore the bundled copy and then tries again", async () => {
render(<RepairView status={status} onRetry={onRetry} />);
fireEvent.click(screen.getByText('looks.repair.restore:{"name":"Fancy"}'));
+13
View File
@@ -120,6 +120,19 @@ describe("layoutStatus", () => {
).toMatchObject({ reason: "failed", error: "server" });
});
it("says failed, not disabled, when the server could not start it", () => {
expect(
layoutStatus(
input({
owner: owner(
{ enabled: false, state: "failed", lastError: "bad bundle" },
"none",
),
}),
),
).toMatchObject({ reason: "failed", error: "bad bundle" });
});
it("says incompatible when the server skipped its plugin", () => {
expect(
layoutStatus(