What happened? / 问题描述
Three components implement two different contracts for contributes.themes[].assets, so a theme plugin that ships its wallpaper/font inside the package passes pi-plugin check, publishes to a catalog, and then cannot be installed by any user:
| Component |
Accepts package-relative? |
Accepts absolute? |
SDK validator isThemeAssetPath (packages/plugin-sdk/src/theme-css.ts) — used by pi-plugin check |
✅ |
✅ |
Runtime loader resolveThemeAssets (apps/desktop/electron/main/plugin-runtime.ts) — imports normalizeThemeAssetPath from the SDK |
✅ (resolveInsidePlugin) |
✅ |
Installer validator normalize_theme_asset_path (crates/host-core/src/plugins/validation.rs) |
❌ |
✅ but the path must already exist on the installing machine |
ADR 0255 ("Theme assets are absolute paths", 2026-09-15, D422) decided absolute-only and states that normalizeThemeAssetPath (SDK) and normalize_theme_asset_path (host-core) both reject package-relative. The host-core side implements that decision; the SDK validator, the SDK doc comments ("may be package-relative or absolute"), the PluginThemeContrib.assets doc, and the loader were never aligned, and the SDK's own tests still assert package-relative acceptance (theme-css.test.ts, index.test.ts).
The result is a dead end for any distributed theme plugin with shipped image/font bytes:
- package-relative asset → installer rejects:
must be an absolute image or font path;
- absolute asset → installer requires the file to already exist on the installing machine (
asset missing) — impossible for a distributed package whose bytes live inside the .piplug;
- the runtime route (data directory +
pi.themes.upsert) is dynamic and cannot be declared in a static manifest at all.
Steps to reproduce / 复现步骤
- PI-Desktop 0.15.6.
- Plugins page → install
local.miku-theme 1.0.0 from the community catalog (AIUO-Net/pi-desktop-plugins) — a plugin whose manifest declares "assets": ["themes/assets/wallpaper-dark.webp"] and which passes pi-plugin check.
- Installation fails with
PLUGIN_INVALID: theme cyber-diva asset themes/assets/wallpaper-dark.webp must be an absolute image or font path.
- The same plugin loaded as a local directory (registry
source: "dev") works: the theme renders and the wallpaper is served through plugin-asset://.
Expected behavior / 预期行为
Author-time validation and the installer agree. Either a package that passes pi-plugin check installs on every machine, or the check rejects it locally with a message that names the migration path — never the current state where the check approves what the installer refuses.
Actual behavior / 实际行为
Installation is rejected on every platform (Linux verified; the same host-core validation runs on Windows/macOS — nothing here is OS-specific). The error text gives the plugin author no hint that the bytes need to move out of the package, and the SDK docs/tests actively bless the rejected spelling.
App version / 应用版本
0.15.6
Operating system / 操作系统
Linux (deb, amd64) — reproduced on the shared host-core validation path used by all platforms.
Extra environment / 其他环境信息
Plugin: local.miku-theme 1.0.0, community catalog (AIUO-Net/pi-desktop-plugins), authored 2026-09-20/22 — after ADR 0255 but against the SDK-documented contract.
Logs / 日志
PLUGIN_INVALID: theme cyber-diva asset themes/assets/wallpaper-dark.webp must be an absolute image or font path
Impact / 影响
- Every catalog theme plugin that follows the SDK documentation and passes
pi-plugin check is uninstallable for every user, with an error that reads like a plugin bug but is a contract mismatch.
- The community catalog accumulates broken packages (review approved
local.miku-theme; pi-plugin check is the gate authors trust).
- Users must fall back to manual directory installs (registry
source: "dev"), which loses install-time validation and updates.
Proposed resolution / 解决方案
Two coherent directions; happy to follow up with either.
A. Complete ADR 0255 on the TS side (offered as PR #936-ready) — pi-plugin check fails package-relative assets at author time with an actionable message (bytes → pi.plugin.getDataPath(), reference by absolute path, or register at runtime via pi.themes.upsert). The shared normalizeThemeAssetPath, the SDK docs/tests, and the PluginThemeContrib.assets doc can then be aligned in a follow-up; whether the loader should stop resolving package-relative for already-installed plugins is a breaking change ADR 0255 already accepts, but the scheduling is a maintainer call.
B. Reconsider ADR 0255 for the installer — accept package-relative again (resolving inside the package root with the existing containment rules). I have a tested host-core patch implementing this (643 tests green), but I understand "allow both spellings" was explicitly rejected in ADR 0255's alternatives, so this direction needs an owner decision, not just code.
中文摘要:SDK 校验器/加载器与 host-core 安装器对主题资源路径的契约不一致(前者按 ADR 0248 接受包内相对路径,后者按 ADR 0255 只认绝对路径且要求文件已存在于用户机器)。结果是:遵循 SDK 文档、通过 pi-plugin check 的主题插件对任何用户都无法安装,且报错无迁移指引。与操作系统无关。方向 A(补齐 ADR 0255 在 TS 侧的落地,PR 已就绪)或方向 B(安装器重新接受包内相对路径,补丁已备好)二选一即可闭环。
What happened? / 问题描述
Three components implement two different contracts for
contributes.themes[].assets, so a theme plugin that ships its wallpaper/font inside the package passespi-plugin check, publishes to a catalog, and then cannot be installed by any user:isThemeAssetPath(packages/plugin-sdk/src/theme-css.ts) — used bypi-plugin checkresolveThemeAssets(apps/desktop/electron/main/plugin-runtime.ts) — importsnormalizeThemeAssetPathfrom the SDKresolveInsidePlugin)normalize_theme_asset_path(crates/host-core/src/plugins/validation.rs)ADR 0255 ("Theme assets are absolute paths", 2026-09-15, D422) decided absolute-only and states that
normalizeThemeAssetPath(SDK) andnormalize_theme_asset_path(host-core) both reject package-relative. The host-core side implements that decision; the SDK validator, the SDK doc comments ("may be package-relative or absolute"), thePluginThemeContrib.assetsdoc, and the loader were never aligned, and the SDK's own tests still assert package-relative acceptance (theme-css.test.ts,index.test.ts).The result is a dead end for any distributed theme plugin with shipped image/font bytes:
must be an absolute image or font path;asset missing) — impossible for a distributed package whose bytes live inside the.piplug;pi.themes.upsert) is dynamic and cannot be declared in a static manifest at all.Steps to reproduce / 复现步骤
local.miku-theme1.0.0 from the community catalog (AIUO-Net/pi-desktop-plugins) — a plugin whose manifest declares"assets": ["themes/assets/wallpaper-dark.webp"]and which passespi-plugin check.PLUGIN_INVALID: theme cyber-diva asset themes/assets/wallpaper-dark.webp must be an absolute image or font path.source: "dev") works: the theme renders and the wallpaper is served throughplugin-asset://.Expected behavior / 预期行为
Author-time validation and the installer agree. Either a package that passes
pi-plugin checkinstalls on every machine, or the check rejects it locally with a message that names the migration path — never the current state where the check approves what the installer refuses.Actual behavior / 实际行为
Installation is rejected on every platform (Linux verified; the same host-core validation runs on Windows/macOS — nothing here is OS-specific). The error text gives the plugin author no hint that the bytes need to move out of the package, and the SDK docs/tests actively bless the rejected spelling.
App version / 应用版本
0.15.6
Operating system / 操作系统
Linux (deb, amd64) — reproduced on the shared host-core validation path used by all platforms.
Extra environment / 其他环境信息
Plugin:
local.miku-theme1.0.0, community catalog (AIUO-Net/pi-desktop-plugins), authored 2026-09-20/22 — after ADR 0255 but against the SDK-documented contract.Logs / 日志
Impact / 影响
pi-plugin checkis uninstallable for every user, with an error that reads like a plugin bug but is a contract mismatch.local.miku-theme;pi-plugin checkis the gate authors trust).source: "dev"), which loses install-time validation and updates.Proposed resolution / 解决方案
Two coherent directions; happy to follow up with either.
A. Complete ADR 0255 on the TS side (offered as PR #936-ready) —
pi-plugin checkfails package-relative assets at author time with an actionable message (bytes →pi.plugin.getDataPath(), reference by absolute path, or register at runtime viapi.themes.upsert). The sharednormalizeThemeAssetPath, the SDK docs/tests, and thePluginThemeContrib.assetsdoc can then be aligned in a follow-up; whether the loader should stop resolving package-relative for already-installed plugins is a breaking change ADR 0255 already accepts, but the scheduling is a maintainer call.B. Reconsider ADR 0255 for the installer — accept package-relative again (resolving inside the package root with the existing containment rules). I have a tested host-core patch implementing this (643 tests green), but I understand "allow both spellings" was explicitly rejected in ADR 0255's alternatives, so this direction needs an owner decision, not just code.
中文摘要:SDK 校验器/加载器与 host-core 安装器对主题资源路径的契约不一致(前者按 ADR 0248 接受包内相对路径,后者按 ADR 0255 只认绝对路径且要求文件已存在于用户机器)。结果是:遵循 SDK 文档、通过
pi-plugin check的主题插件对任何用户都无法安装,且报错无迁移指引。与操作系统无关。方向 A(补齐 ADR 0255 在 TS 侧的落地,PR 已就绪)或方向 B(安装器重新接受包内相对路径,补丁已备好)二选一即可闭环。