07038018ac
* fix(plugins): unload previous version before reloading on plugin update The install/update path reloaded a plugin without first unloading the previous version's registrations, unlike disable/uninstall. Because the slot registry only appends, this ran initialize() twice and left two main-top-bar slot entries, duplicating the plugin's widget icon until a page refresh. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(plugins): cache-bust plugin bundle import so updates re-execute unloadPlugin(id, {evictCache: true}) drops the cached bundle registration on install/update, but a same-tab re-import() of the identical bundle URL returns the browser's already-evaluated ES module without re-running its top-level registerKandevPlugin() call — leaving an updated plugin active but missing from the UI. Key the bundle URL on the plugin's installed version (?v=<version>) on both the frontend (toActivePlugin) and the backend boot payload (bootActivePlugins) so a version bump forces a real re-import while an unchanged version stays cache-friendly across boots. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(plugins): update bundleUrl assertions for cache-bust query param toActivePlugin now appends a ?v=<version> cache-busting query param to bundleUrl; these two assertions still expected the bare path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(plugins): only evict bundle cache on install/update, not on enable loadIfActive unconditionally evicted the cached bundle registration, which broke the disable->enable cycle: since bundleUrl doesn't change on plain enable, the browser's ES module cache returned the already-evaluated module without re-running registerKandevPlugin, leaving the plugin's slots/routes unregistered until a full page refresh. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
44 lines
1.8 KiB
TypeScript
44 lines
1.8 KiB
TypeScript
/**
|
|
* Builds the `ActivePlugin` shape (docs/plans/plugins/PLUGIN-API.md)
|
|
* for a plugin the operator just enabled, so the settings page can trigger a
|
|
* runtime `loadPlugins` call without a full page reload. Mirrors the
|
|
* backend's boot-payload mapping in
|
|
* apps/backend/internal/backendapp/helpers.go (bundleActivePlugins /
|
|
* pluginStyleURLs) — keep both in sync.
|
|
*/
|
|
import type { ActivePlugin } from "./types";
|
|
import type { PluginRecord } from "@/lib/types/plugins";
|
|
|
|
/**
|
|
* Returns the `ActivePlugin` for a plugin record, or null when the plugin
|
|
* declares no UI bundle (nothing to load).
|
|
*
|
|
* `bundleUrl` carries the installed `version` as a `?v=` query param so an
|
|
* updated plugin resolves to a *different* module specifier. Without this,
|
|
* `unloadPlugin(id, { evictCache: true })` (see `lib/plugins/host.ts`) drops
|
|
* the cached bundle registration, but a same-tab re-`import()` of the same
|
|
* URL returns the browser's already-evaluated ES module without re-running
|
|
* its top-level `registerKandevPlugin()` — leaving the plugin active but
|
|
* unregistered. Keying the URL on version keeps a plain unchanged
|
|
* enable/disable cycle or reload cache-friendly (identical URL, browser
|
|
* cache hit) while forcing a real re-import + re-execution whenever the
|
|
* version actually changes.
|
|
*/
|
|
export function toActivePlugin(record: PluginRecord): ActivePlugin | null {
|
|
const bundle = record.ui?.bundle;
|
|
if (!bundle) return null;
|
|
|
|
return {
|
|
id: record.id,
|
|
name: record.display_name,
|
|
bundleUrl: `/api/plugins/${record.id}/bundle?v=${encodeURIComponent(record.version)}`,
|
|
styleUrls: pluginStyleURLs(record),
|
|
};
|
|
}
|
|
|
|
function pluginStyleURLs(record: PluginRecord): string[] | undefined {
|
|
const styles = record.ui?.styles;
|
|
if (!styles || styles.length === 0) return undefined;
|
|
return styles.map((style) => `/api/plugins/${record.id}/ui${style}`);
|
|
}
|