fix(plugins): rename updates plugin id

This commit is contained in:
Federico Jaramillo Martinez
2026-06-03 22:53:37 +02:00
parent 76302a4e5f
commit be02c3516f
7 changed files with 78 additions and 38 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@jmfederico/pi-web": patch
---
Rename the built-in Updates plugin id from `pi-web` to `updates` for clearer plugin configuration.
+1 -1
View File
@@ -101,7 +101,7 @@ PI WEB keeps its own state intentionally small:
PI WEB production installs can load trusted local UI plugins without rebuilding PI WEB. Plugins are browser-side ES modules that can add action-palette actions, workspace panels, and workspace-label metadata. They do not run in the session daemon and are not sandboxed. PI WEB production installs can load trusted local UI plugins without rebuilding PI WEB. Plugins are browser-side ES modules that can add action-palette actions, workspace panels, and workspace-label metadata. They do not run in the session daemon and are not sandboxed.
The supported package shape is intentionally singular: `piWeb.plugins` entries with explicit `id` and `module`, plus a browser module that exports `{ apiVersion: 1, name, activate }`. The bundled `pi-web-plugins/info` TypeScript source is the canonical minimal real example, `pi-web-plugins/pi-web` demonstrates a dynamic status panel, and built-in [Workspace Tasks](docs/plugins.md#workspace-tasks) adds a workspace tab for running configured shell commands in PI WEB terminals. The supported package shape is intentionally singular: `piWeb.plugins` entries with explicit `id` and `module`, plus a browser module that exports `{ apiVersion: 1, name, activate }`. The bundled `pi-web-plugins/info` TypeScript source is the canonical minimal real example, `pi-web-plugins/updates` demonstrates a dynamic status panel, and built-in [Workspace Tasks](docs/plugins.md#workspace-tasks) adds a workspace tab for running configured shell commands in PI WEB terminals.
A useful prompt for AI agents: A useful prompt for AI agents:
+21 -1
View File
@@ -162,7 +162,7 @@ After editing, check the manifest endpoint and browser-console failure cases.</c
If you copy it, choose a new plugin id so it does not conflict with the bundled <code>info</code> plugin. If you copy it, choose a new plugin id so it does not conflict with the bundled <code>info</code> plugin.
</p> </p>
<p> <p>
The bundled <code>pi-web</code> status plugin demonstrates dynamic <code>visible</code> and <code>badge</code> The bundled <code>updates</code> plugin demonstrates dynamic <code>visible</code> and <code>badge</code>
callbacks for tabs that only appear when the host has status messages or needs extra install visibility. callbacks for tabs that only appear when the host has status messages or needs extra install visibility.
</p> </p>
</section> </section>
@@ -179,6 +179,26 @@ After editing, check the manifest endpoint and browser-console failure cases.</c
<code>plugins</code> config key. <code>plugins</code> config key.
</p> </p>
<h3>Updates</h3>
<p>
<strong>Updates</strong> adds a conditional <strong>Updates</strong> workspace tab with PI WEB update,
restart, and installed-service guidance. It is built into PI WEB and enabled by default.
</p>
<ul>
<li>Plugin id: <code>updates</code></li>
</ul>
<div class="code-card">
<div class="copy-row">
<strong>Disable Updates</strong>
<button class="copy-button" data-copy="#updates-disable">Copy</button>
</div>
<pre id="updates-disable"><code>{
"plugins": {
"updates": { "enabled": false }
}
}</code></pre>
</div>
<h3>Workspace Tasks</h3> <h3>Workspace Tasks</h3>
<p> <p>
<strong>Workspace Tasks</strong> adds a <strong>Tasks</strong> workspace tab for running configured shell <strong>Workspace Tasks</strong> adds a <strong>Tasks</strong> workspace tab for running configured shell
+16 -1
View File
@@ -112,7 +112,7 @@ export default {
When copying the Info plugin, choose a new plugin id so it does not conflict with the bundled `info` plugin. When copying the Info plugin, choose a new plugin id so it does not conflict with the bundled `info` plugin.
PI WEB also ships a `pi-web` status plugin that demonstrates dynamic `visible` and `badge` callbacks for tabs that only appear when the host has status messages or needs extra install visibility. PI WEB also ships an `updates` plugin that demonstrates dynamic `visible` and `badge` callbacks for tabs that only appear when the host has status messages or needs extra install visibility.
## Local plugin usage ## Local plugin usage
@@ -157,6 +157,21 @@ PI WEB ships core, discoverable plugins in the main `@jmfederico/pi-web` npm pac
Built-in plugins can be managed from **Settings → Plugins** or with the top-level `plugins` config key. Built-in plugins can be managed from **Settings → Plugins** or with the top-level `plugins` config key.
### Updates
**Plugin id:** `updates`
**What it does:** adds a conditional **Updates** workspace tab with PI WEB update, restart, and installed-service guidance.
Updates is enabled by default. To hide it, disable `updates` in **Settings → Plugins** or set:
```json
{
"plugins": {
"updates": { "enabled": false }
}
}
```
### Workspace Tasks ### Workspace Tasks
**Plugin id:** `workspace-tasks` **Plugin id:** `workspace-tasks`
-9
View File
@@ -1,9 +0,0 @@
{
"name": "@pi-web/status-plugin",
"private": true,
"piWeb": {
"plugins": [
{ "id": "pi-web", "module": "pi-web-plugin.js" }
]
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"name": "@pi-web/updates-plugin",
"private": true,
"piWeb": {
"plugins": [
{ "id": "updates", "module": "pi-web-plugin.js" }
]
}
}
@@ -19,7 +19,7 @@ function isLocalOrUnknownInstallation(installation: PiWebInstallationInfo | unde
return installation === undefined || installation.kind === "local" || installation.kind === "unknown"; return installation === undefined || installation.kind === "local" || installation.kind === "unknown";
} }
function shouldShowStatusPanel(state: AppState): boolean { function shouldShowUpdatesPanel(state: AppState): boolean {
const status = statusFor(state); const status = statusFor(state);
if (messageCount(state) > 0) return true; if (messageCount(state) > 0) return true;
if (status === undefined) return false; if (status === undefined) return false;
@@ -50,7 +50,7 @@ function renderComponent(html: HtmlTemplateTag, component: PiWebComponentStatus)
? "restart needed" ? "restart needed"
: "current"; : "current";
return html` return html`
<div class="pi-web-version-row"> <div class="updates-version-row">
<strong>${component.label}</strong> <strong>${component.label}</strong>
<span>${status}</span> <span>${status}</span>
<small>running ${formatVersion(component.runtimeVersion)} · installed ${formatVersion(component.installedVersion)}</small> <small>running ${formatVersion(component.runtimeVersion)} · installed ${formatVersion(component.installedVersion)}</small>
@@ -61,7 +61,7 @@ function renderComponent(html: HtmlTemplateTag, component: PiWebComponentStatus)
function renderCommand(html: HtmlTemplateTag, label: string, command: string): TemplateResult { function renderCommand(html: HtmlTemplateTag, label: string, command: string): TemplateResult {
return html` return html`
<div class="pi-web-command"> <div class="updates-command">
<span>${label}</span> <span>${label}</span>
<code>${command}</code> <code>${command}</code>
<button @click=${() => { void navigator.clipboard.writeText(command); }}>Copy</button> <button @click=${() => { void navigator.clipboard.writeText(command); }}>Copy</button>
@@ -87,7 +87,7 @@ function renderCommands(html: HtmlTemplateTag, status: PiWebStatusResponse): Tem
`; `;
} }
function renderStatusPanel(html: HtmlTemplateTag, state: AppState): TemplateResult { function renderUpdatesPanel(html: HtmlTemplateTag, state: AppState): TemplateResult {
const status = statusFor(state); const status = statusFor(state);
if (status === undefined) { if (status === undefined) {
return html` return html`
@@ -99,29 +99,29 @@ function renderStatusPanel(html: HtmlTemplateTag, state: AppState): TemplateResu
const messages = status.messages; const messages = status.messages;
return html` return html`
<style> <style>
.viewer.pi-web-status { flex: 1 1 auto; min-height: 0; box-sizing: border-box; display: flex; flex-direction: column; gap: 14px; padding: 12px; overflow-y: auto; overflow-x: hidden; } .viewer.updates-status { flex: 1 1 auto; min-height: 0; box-sizing: border-box; display: flex; flex-direction: column; gap: 14px; padding: 12px; overflow-y: auto; overflow-x: hidden; }
.viewer.pi-web-status section { flex: 0 0 auto; min-width: 0; display: grid; gap: 8px; } .viewer.updates-status section { flex: 0 0 auto; min-width: 0; display: grid; gap: 8px; }
.pi-web-message { display: grid; gap: 5px; border: 1px solid var(--pi-border); border-radius: 8px; padding: 10px; background: var(--pi-surface); } .updates-message { display: grid; gap: 5px; border: 1px solid var(--pi-border); border-radius: 8px; padding: 10px; background: var(--pi-surface); }
.pi-web-message.warning { border-color: var(--pi-warning-border); background: var(--pi-warning-surface); } .updates-message.warning { border-color: var(--pi-warning-border); background: var(--pi-warning-surface); }
.pi-web-message.error { border-color: var(--pi-danger); } .updates-message.error { border-color: var(--pi-danger); }
.pi-web-message-title { display: flex; gap: 8px; align-items: baseline; } .updates-message-title { display: flex; gap: 8px; align-items: baseline; }
.pi-web-message-title span { color: var(--pi-muted); font-size: 12px; text-transform: uppercase; } .updates-message-title span { color: var(--pi-muted); font-size: 12px; text-transform: uppercase; }
.pi-web-version-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 3px 10px; border-bottom: 1px solid var(--pi-border-muted); padding: 6px 0; } .updates-version-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 3px 10px; border-bottom: 1px solid var(--pi-border-muted); padding: 6px 0; }
.pi-web-version-row small { grid-column: 1 / -1; color: var(--pi-muted); } .updates-version-row small { grid-column: 1 / -1; color: var(--pi-muted); }
.pi-web-command { min-width: 0; display: grid; grid-template-columns: minmax(90px, auto) minmax(0, 1fr) auto; gap: 8px; align-items: center; } .updates-command { min-width: 0; display: grid; grid-template-columns: minmax(90px, auto) minmax(0, 1fr) auto; gap: 8px; align-items: center; }
.pi-web-command code { overflow: auto; border: 1px solid var(--pi-border-muted); border-radius: 6px; background: var(--pi-bg); padding: 5px 7px; white-space: nowrap; } .updates-command code { overflow: auto; border: 1px solid var(--pi-border-muted); border-radius: 6px; background: var(--pi-bg); padding: 5px 7px; white-space: nowrap; }
.pi-web-meta { display: grid; gap: 2px; color: var(--pi-muted); font-size: 12px; } .updates-meta { display: grid; gap: 2px; color: var(--pi-muted); font-size: 12px; }
@media (max-width: 520px) { @media (max-width: 520px) {
.pi-web-command { grid-template-columns: minmax(0, 1fr) auto; } .updates-command { grid-template-columns: minmax(0, 1fr) auto; }
.pi-web-command > span { grid-column: 1 / -1; } .updates-command > span { grid-column: 1 / -1; }
} }
</style> </style>
<section class="toolbar"><strong>Updates</strong><span class="stale">beta</span>${messages.length > 0 ? html`<span class="stale">${String(messages.length)}</span>` : null}</section> <section class="toolbar"><strong>Updates</strong><span class="stale">beta</span>${messages.length > 0 ? html`<span class="stale">${String(messages.length)}</span>` : null}</section>
<section class="viewer pi-web-status"> <section class="viewer updates-status">
<section> <section>
${messages.length === 0 ? html`<p class="muted">No PI WEB update or restart messages.</p>` : messages.map((message) => html` ${messages.length === 0 ? html`<p class="muted">No PI WEB update or restart messages.</p>` : messages.map((message) => html`
<article class=${`pi-web-message ${message.severity}`}> <article class=${`updates-message ${message.severity}`}>
<div class="pi-web-message-title"><strong>${message.title}</strong><span>${message.severity}</span></div> <div class="updates-message-title"><strong>${message.title}</strong><span>${message.severity}</span></div>
<p>${message.body}</p> <p>${message.body}</p>
${message.command === undefined ? null : html`<code>${message.command}</code>`} ${message.command === undefined ? null : html`<code>${message.command}</code>`}
</article> </article>
@@ -136,7 +136,7 @@ function renderStatusPanel(html: HtmlTemplateTag, state: AppState): TemplateResu
${renderCommands(html, status)} ${renderCommands(html, status)}
<section class="pi-web-meta"> <section class="updates-meta">
<span>Generated ${status.generatedAt}</span> <span>Generated ${status.generatedAt}</span>
${status.release.latestVersion === undefined ? null : html`<span>Latest npm release ${status.release.latestVersion}</span>`} ${status.release.latestVersion === undefined ? null : html`<span>Latest npm release ${status.release.latestVersion}</span>`}
${status.release.skipped === true ? html`<span>Remote version check skipped.</span>` : null} ${status.release.skipped === true ? html`<span>Remote version check skipped.</span>` : null}
@@ -148,12 +148,12 @@ function renderStatusPanel(html: HtmlTemplateTag, state: AppState): TemplateResu
const plugin: PiWebPlugin = { const plugin: PiWebPlugin = {
apiVersion: 1, apiVersion: 1,
name: "PI WEB Updates", name: "Updates",
activate: ({ html, svg }) => ({ activate: ({ html, svg }) => ({
contributions: { contributions: {
workspacePanels: [ workspacePanels: [
{ {
id: "workspace.status", id: "workspace.updates",
title: "Updates", title: "Updates",
icon: svg` icon: svg`
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
@@ -164,12 +164,12 @@ const plugin: PiWebPlugin = {
</svg> </svg>
`, `,
order: 100, order: 100,
visible: (context) => shouldShowStatusPanel(context.state), visible: (context) => shouldShowUpdatesPanel(context.state),
badge: (context) => { badge: (context) => {
const count = messageCount(context.state); const count = messageCount(context.state);
return html`beta${count > 0 ? html` · ${String(count)}` : null}`; return html`beta${count > 0 ? html` · ${String(count)}` : null}`;
}, },
render: (context) => renderStatusPanel(html, context.state), render: (context) => renderUpdatesPanel(html, context.state),
}, },
], ],
}, },