libs 0019: remote lib edit — invalidate, don't auto-reload; editor copy counted + re-read

- kicadLibsInvalidate: a peer's edit only drops the lib's plugin entry + pcbnew's
  PreloadedFootprints (the cache the old reload never cleared — tree/preview/
  LoadFootprint/update-from-library kept serving the old body); the fat re-load
  now runs lazily or from Update-from-library (kicadLibsReload, which also
  clears the preloaded cache)
- usage bridges count the Footprint/Symbol Editor's open copy; update re-opens
  an unmodified copy, reports a modified one
- embind TU gets eeschema/symbol_editor on its include path; smoke probes
- kicad → 27051b46e2

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Wd1r3ewftpV1DBSEArpRa
This commit is contained in:
Gergő Törcsvári 2026-08-26 13:29:29 +02:00
commit 767f2abfc0
No known key found for this signature in database
GPG key ID: 8E75F2CDE64E5322
8 changed files with 307 additions and 10 deletions

View file

@ -90,8 +90,11 @@ export function useLibNotices(opts: {
}, []);
/** Update every placed instance of the stale items from the library (2c). */
const updateStaleFromLibrary = React.useCallback(async () => {
const mod = (window as { Module?: { kicadUpdateFromLibrary?: unknown } }).Module;
const mod = (window as {
Module?: { kicadUpdateFromLibrary?: unknown; kicadLibsReload?: unknown };
}).Module;
const fn = mod?.kicadUpdateFromLibrary;
const reload = mod?.kicadLibsReload;
if (typeof fn !== "function") {
setLibError("This editor build can't update placed items from the library — reload to refresh them.");
return;
@ -102,10 +105,20 @@ export function useLibNotices(opts: {
// The bridge queues the edit on the frame's coroutine and answers
// {queued:true}; the outcome arrives as a `pcbjam:lib-update-done`
// window event (or {ok:false,error} synchronously).
const done = new Promise<{ ok: boolean; updated?: number; error?: string }>((resolve) => {
// libs 0019 F2: the remote edit only invalidated the caches — bring the
// lib back to LOADED first (same coroutine queue, so it lands before
// the exchange below).
if (typeof reload === "function") {
try {
(reload as (kind: string, lib: string) => void)(entry.kind, entry.lib);
} catch {
/* the update below falls back to the lazy load */
}
}
const done = new Promise<{ ok: boolean; updated?: number; missing?: string[]; error?: string }>((resolve) => {
const onDone = (e: Event) => {
window.removeEventListener("pcbjam:lib-update-done", onDone);
resolve((e as CustomEvent<{ ok: boolean; updated?: number }>).detail);
resolve((e as CustomEvent<{ ok: boolean; updated?: number; missing?: string[] }>).detail);
};
window.addEventListener("pcbjam:lib-update-done", onDone);
setTimeout(() => {
@ -131,6 +144,10 @@ export function useLibNotices(opts: {
continue;
}
console.log(`[libs] updated ${outcome.updated ?? "?"} placed ${entry.kind}(s) from "${entry.lib}"`);
// An open editor copy with local edits is left alone (0019 F3) — say so.
if (outcome.missing && outcome.missing.length > 0) {
setLibError(`Not updated: ${outcome.missing.join("; ")}`);
}
clearStale(key);
}
} finally {

View file

@ -111,13 +111,21 @@ export function syncedLibsSource(
const names = [...(pendingNames.get(kind) ?? [])];
pendingNames.delete(kind);
const mod = (globalThis as { Module?: Record<string, unknown> }).Module;
// libs 0019 F2: a remote edit only INVALIDATES the editor's caches
// (cheap); the fat re-load happens lazily when the user looks at the
// lib, or explicitly from "Update from library" (kicadLibsReload).
// Older builds without the invalidate export keep the eager reload.
const invalidate = mod?.kicadLibsInvalidate;
const reload = mod?.kicadLibsReload;
if (typeof reload !== "function") return;
log(`[synced] remote change → reload ${kind} lib "${info.name}"`);
const fn = typeof invalidate === "function" ? invalidate : reload;
if (typeof fn !== "function") return;
log(
`[synced] remote change → ${fn === invalidate ? "invalidate" : "reload"} ${kind} lib "${info.name}"`,
);
try {
(reload as (kind: string, nickname: string) => void)(kind, info.name);
(fn as (kind: string, nickname: string) => void)(kind, info.name);
} catch (e) {
log(`[synced] editor reload failed: ${String(e)}`);
log(`[synced] editor cache invalidation failed: ${String(e)}`);
return;
}
emitItemUpdated(info, kind, names, mod);