fix: keep federated docker updates tab visible

This commit is contained in:
Pi Web Agent
2026-06-29 16:12:40 +00:00
parent 4885aa0dd5
commit 01c75298f9
8 changed files with 136 additions and 10 deletions
+14 -3
View File
@@ -1,6 +1,6 @@
import type { TemplateResult } from "lit";
import type { HtmlTemplateTag, PiWebComponentStatus, PiWebPlugin, PiWebStatusResponse, PluginRuntimeState, WorkspacePanelTerminal } from "@jmfederico/pi-web/plugin-api";
import { additionalCommands, formatVersion, installationLabel, messageCount, recommendedCommand, shouldShowUpdatesPanel, statusFor } from "./updatesLogic.js";
import { additionalCommands, fallbackDockerStatus, formatVersion, installationLabel, messageCount, recommendedCommand, shouldShowUpdatesPanel, statusFor, type UpdatesRuntimeHint } from "./updatesLogic.js";
function runCommandInTerminal(terminal: WorkspacePanelTerminal, label: string, command: string): void {
void terminal.runCommand({
@@ -48,6 +48,17 @@ function renderCommand(html: HtmlTemplateTag, terminal: WorkspacePanelTerminal |
`;
}
function updatesRuntimeHintFromModuleUrl(moduleUrl: string): UpdatesRuntimeHint {
try {
const dockerMode = new URL(moduleUrl).searchParams.get("piWebDockerMode");
return dockerMode === "runtime" || dockerMode === "dev" ? { dockerMode } : {};
} catch {
return {};
}
}
const runtimeHint = updatesRuntimeHintFromModuleUrl(import.meta.url);
function renderCommands(html: HtmlTemplateTag, terminal: WorkspacePanelTerminal | undefined, status: PiWebStatusResponse): TemplateResult | undefined {
const recommended = recommendedCommand(status);
const additional = additionalCommands(status, recommended);
@@ -71,7 +82,7 @@ function renderCommands(html: HtmlTemplateTag, terminal: WorkspacePanelTerminal
}
function renderUpdatesPanel(html: HtmlTemplateTag, terminal: WorkspacePanelTerminal | undefined, state: PluginRuntimeState | undefined): TemplateResult {
const status = statusFor(state);
const status = statusFor(state) ?? fallbackDockerStatus(runtimeHint);
if (status === undefined) {
return html`
<section class="toolbar"><strong>Updates</strong></section>
@@ -157,7 +168,7 @@ const plugin: PiWebPlugin = {
</svg>
`,
order: 100,
visible: (context) => shouldShowUpdatesPanel(context.state),
visible: (context) => shouldShowUpdatesPanel(context.state, runtimeHint),
badge: (context) => {
const count = messageCount(context.state);
return html`beta${count > 0 ? html` · ${String(count)}` : null}`;
+24 -1
View File
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
import type { PiWebComponentStatus, PiWebStatusMessage, PiWebStatusResponse, PluginRuntimeState } from "@jmfederico/pi-web/plugin-api";
import { additionalCommands, formatVersion, installationLabel, messageCount, recommendedCommand, shouldShowUpdatesPanel } from "./updatesLogic";
import { additionalCommands, fallbackDockerStatus, formatVersion, installationLabel, messageCount, recommendedCommand, shouldShowUpdatesPanel } from "./updatesLogic";
function component(overrides: Partial<PiWebComponentStatus> = {}): PiWebComponentStatus {
return {
@@ -181,6 +181,11 @@ describe("shouldShowUpdatesPanel", () => {
expect(shouldShowUpdatesPanel(stateWith(value))).toBe(true);
});
it("shows the panel when a federated Docker runtime hint is available before status is parsed", () => {
expect(shouldShowUpdatesPanel(undefined, { dockerMode: "dev" })).toBe(true);
expect(shouldShowUpdatesPanel(undefined, { dockerMode: "runtime" })).toBe(true);
});
it("hides the panel when status is unavailable", () => {
expect(shouldShowUpdatesPanel(stateWith(undefined))).toBe(false);
expect(shouldShowUpdatesPanel(undefined)).toBe(false);
@@ -223,6 +228,24 @@ describe("shouldShowUpdatesPanel", () => {
});
});
describe("fallbackDockerStatus", () => {
it("creates Docker development commands from a federated runtime hint", () => {
const fallback = fallbackDockerStatus({ dockerMode: "dev" }, "generated");
expect(fallback?.generatedAt).toBe("generated");
expect(fallback?.components.web.installation).toEqual({ kind: "docker", dockerMode: "dev" });
expect(fallback?.commands).toMatchObject({
update: "pi-web-docker --dev update",
restart: "pi-web-docker --dev restart",
status: "pi-web-docker --dev status",
});
expect(fallback?.messages[0]?.id).toBe("docker-status-compatibility");
});
it("does not create a fallback without a Docker runtime hint", () => {
expect(fallbackDockerStatus({})).toBeUndefined();
});
});
describe("messageCount", () => {
it("counts messages and tolerates missing status", () => {
expect(messageCount(undefined)).toBe(0);
+35 -2
View File
@@ -1,10 +1,14 @@
import type { PiWebInstallationInfo, PiWebStatusMessage, PiWebStatusResponse, PluginRuntimeState } from "@jmfederico/pi-web/plugin-api";
import type { PiWebDockerMode, PiWebInstallationInfo, PiWebStatusMessage, PiWebStatusResponse, PluginRuntimeState } from "@jmfederico/pi-web/plugin-api";
export interface CommandEntry {
label: string;
command: string;
}
export interface UpdatesRuntimeHint {
dockerMode?: PiWebDockerMode;
}
// The single command users should run when they do not want to think: if an
// update is available, `commands.update` already chains the update and a full
// restart; otherwise, when anything is stale, a full restart is enough.
@@ -49,14 +53,43 @@ export function isSelfManagedInstallation(installation: PiWebInstallationInfo |
return installation === undefined || installation.kind === "local" || installation.kind === "docker" || installation.kind === "unknown";
}
export function shouldShowUpdatesPanel(state: PluginRuntimeState | undefined): boolean {
export function shouldShowUpdatesPanel(state: PluginRuntimeState | undefined, hint: UpdatesRuntimeHint = {}): boolean {
const status = statusFor(state);
if (hint.dockerMode !== undefined) return true;
if (messageCount(state) > 0) return true;
if (status === undefined) return false;
return isSelfManagedInstallation(status.components.web.installation)
|| isSelfManagedInstallation(status.components.sessiond.installation);
}
export function fallbackDockerStatus(hint: UpdatesRuntimeHint, generatedAt = "federated status unavailable"): PiWebStatusResponse | undefined {
if (hint.dockerMode === undefined) return undefined;
const commandPrefix = hint.dockerMode === "dev" ? "pi-web-docker --dev" : "pi-web-docker";
const installation: PiWebInstallationInfo = { kind: "docker", dockerMode: hint.dockerMode };
return {
packageName: "@jmfederico/pi-web",
generatedAt,
components: {
web: { component: "web", label: "Web/UI", stale: false, available: true, installation },
sessiond: { component: "sessiond", label: "Session daemon", stale: false, available: true, installation },
},
release: { packageName: "@jmfederico/pi-web", updateAvailable: false, skipped: true },
commands: {
update: `${commandPrefix} update`,
restart: `${commandPrefix} restart`,
restartWeb: `${commandPrefix} restart-web`,
restartSessiond: `${commandPrefix} restart-sessiond`,
status: `${commandPrefix} status`,
},
messages: [{
id: "docker-status-compatibility",
severity: "info",
title: "Docker update commands available",
body: "This Updates plugin was loaded from a Docker PI WEB runtime, but the gateway has not provided Docker-aware status details yet. The Docker maintenance commands below are still available.",
}],
};
}
export function formatVersion(version: string | undefined): string {
return version === undefined || version === "" ? "unknown" : version;
}