feat: add PWA install icon and uppercase branding

This commit is contained in:
Federico Jaramillo Martinez
2026-05-23 20:50:45 +02:00
parent 428f7bb8c2
commit 1546143038
38 changed files with 248 additions and 211 deletions
+1 -1
View File
@@ -59,7 +59,7 @@ describe("buildApp", () => {
expect(emptyListResponse.json<Project[]>()).toEqual([]);
});
it("serves the Pi Web plugin manifest and plugin assets", async () => {
it("serves the PI WEB plugin manifest and plugin assets", async () => {
const manifestResponse = await app.inject({ method: "GET", url: "/pi-web-plugins/manifest.json" });
expect(manifestResponse.statusCode).toBe(200);
expect(manifestResponse.json()).toEqual({ plugins: [{ id: "fake", module: "/pi-web-plugins/fake/plugin.js?v=1", source: "test", scope: "local" }] });
+9 -9
View File
@@ -203,10 +203,10 @@ async function discoverPackageRoot(root: string, configuredPackage: ConfiguredPi
async function discoverPluginEntries(root: string, config: PiWebPackageConfig): Promise<ArraylessPluginRecord[]> {
const plugins: ArraylessPluginRecord[] = [];
for (const entry of config.plugins) {
if (!isSafeRelativePath(entry.module)) throw new Error(`Unsafe Pi Web plugin module path for ${entry.id}: ${entry.module}`);
if (!isSafeRelativePath(entry.module)) throw new Error(`Unsafe PI WEB plugin module path for ${entry.id}: ${entry.module}`);
const entryPath = join(root, entry.module);
const entryStat = await stat(entryPath).catch(() => undefined);
if (entryStat?.isFile() !== true) throw new Error(`Pi Web plugin module not found for ${entry.id}: ${entry.module}`);
if (entryStat?.isFile() !== true) throw new Error(`PI WEB plugin module not found for ${entry.id}: ${entry.module}`);
plugins.push({ id: entry.id, root, entryFile: entry.module, version: String(Math.floor(entryStat.mtimeMs)) });
}
return plugins;
@@ -227,24 +227,24 @@ async function readPiWebPackageConfig(root: string): Promise<PiWebPackageConfig
}
function parsePluginEntries(piWeb: Record<string, unknown>, packagePath: string): PiWebPluginEntry[] {
if (piWeb["plugin"] !== undefined) throw new Error(`Unsupported Pi Web plugin metadata in ${packagePath}: use piWeb.plugins with { id, module } entries`);
if (piWeb["plugin"] !== undefined) throw new Error(`Unsupported PI WEB plugin metadata in ${packagePath}: use piWeb.plugins with { id, module } entries`);
const plugins = piWeb["plugins"];
if (plugins === undefined) return [];
if (!Array.isArray(plugins)) throw new Error(`Pi Web plugins must be an array in ${packagePath}`);
if (!Array.isArray(plugins)) throw new Error(`PI WEB plugins must be an array in ${packagePath}`);
return plugins.map((entry, index): PiWebPluginEntry => {
if (!isRecord(entry)) throw new Error(`Pi Web plugin entry ${String(index + 1)} must be an object in ${packagePath}`);
if (!isRecord(entry)) throw new Error(`PI WEB plugin entry ${String(index + 1)} must be an object in ${packagePath}`);
const id = entry["id"];
const module = entry["module"];
if (typeof id !== "string" || !pluginIdPattern.test(id)) throw new Error(`Invalid Pi Web plugin id in ${packagePath}: ${String(id)}`);
if (typeof module !== "string" || module === "") throw new Error(`Invalid Pi Web plugin module for ${id} in ${packagePath}`);
if (typeof id !== "string" || !pluginIdPattern.test(id)) throw new Error(`Invalid PI WEB plugin id in ${packagePath}: ${String(id)}`);
if (typeof module !== "string" || module === "") throw new Error(`Invalid PI WEB plugin module for ${id} in ${packagePath}`);
return { id, module };
});
}
function addUnique(records: Map<string, PluginRecord>, plugin: PluginRecord): void {
if (records.has(plugin.id)) {
warnInvalidPlugin(plugin.source, `Duplicate Pi Web plugin id: ${plugin.id}`);
warnInvalidPlugin(plugin.source, `Duplicate PI WEB plugin id: ${plugin.id}`);
return;
}
records.set(plugin.id, plugin);
@@ -252,7 +252,7 @@ function addUnique(records: Map<string, PluginRecord>, plugin: PluginRecord): vo
function warnInvalidPlugin(source: string, error: unknown): void {
const message = error instanceof Error ? error.message : String(error);
console.warn(`Skipping Pi Web plugin from ${source}: ${message}`);
console.warn(`Skipping PI WEB plugin from ${source}: ${message}`);
}
function isSafeRelativePath(path: string): boolean {
+1 -1
View File
@@ -10,7 +10,7 @@ afterEach(() => {
vi.restoreAllMocks();
});
describe("Pi Web status", () => {
describe("PI WEB status", () => {
it("compares semver-shaped CalVer versions", () => {
expect(comparePackageVersions("1.202605.9", "1.202605.8")).toBeGreaterThan(0);
expect(comparePackageVersions("1.202605.8", "1.202605.8")).toBe(0);
+3 -3
View File
@@ -309,8 +309,8 @@ function buildMessages(components: PiWebStatusResponse["components"], release: P
messages.push({
id: "update-available",
severity: "info",
title: "Pi Web update available",
body: `Pi Web ${release.latestVersion} is available${installedVersion === undefined ? "" : `; installed version is ${installedVersion}`}. Update Pi Web, then restart Pi Web services.`,
title: "PI WEB update available",
body: `PI WEB ${release.latestVersion} is available${installedVersion === undefined ? "" : `; installed version is ${installedVersion}`}. Update PI WEB, then restart PI WEB services.`,
command: commands.update,
});
}
@@ -330,7 +330,7 @@ function buildMessages(components: PiWebStatusResponse["components"], release: P
id: "sessiond-unavailable",
severity: "warning",
title: "Session daemon version unavailable",
body: `Pi Web could not check the session daemon version${components.sessiond.error === undefined ? "." : `: ${components.sessiond.error}`}`,
body: `PI WEB could not check the session daemon version${components.sessiond.error === undefined ? "." : `: ${components.sessiond.error}`}`,
command: "systemctl --user status pi-web-sessiond.service",
});
} else if (components.sessiond.stale) {