Skip to content

Commit 86c31b1

Browse files
committed
fix(loader): keep install and revert from overriding an explicit disable
Modules.enable now clears the persisted disable (enable() no longer does); Modules.disable persists; and a new transient Modules.unload stops a module without persisting, for callers that unload without the user asking (single- theme enforcement, theme-report, probes) — disable there durably turned the module off. installLocal unloads any prior instance transiently and, when the module is disabled, installs the files without enabling it. removeLocal's revert only re-enables the staged copy when the override was actually running and not disabled, so removing a record can no longer switch the active theme or resurrect a disabled module, and the requires-restart path forgets the disable when nothing staged remains.
1 parent accc6f7 commit 86c31b1

1 file changed

Lines changed: 30 additions & 2 deletions

File tree

src/jsHelper/modularLoader/index.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -529,8 +529,21 @@ async function boot(): Promise<BootReport | null> {
529529
} catch {}
530530
return true;
531531
},
532-
enable: (id: string) => registry.enable(id, report),
532+
// The user turning a module on: load it, and only if that succeeds
533+
// clear the persisted disable. enable() itself no longer touches the
534+
// pref, so this is the one path that records "on".
535+
enable: async (id: string) => {
536+
const ok = await registry.enable(id, report);
537+
if (ok) registry.markEnabled(id);
538+
return ok;
539+
},
533540
disable: (id: string) => registry.disable(id),
541+
// Transient unload: no persisted disable, for callers that stop a
542+
// module without the user asking (single-theme enforcement in the
543+
// store, theme-report's bare-client capture, probes). Using disable()
544+
// there would durably turn the module off — a crash mid-capture left
545+
// the user with no theme booting.
546+
unload: (id: string) => registry.unload(id),
534547
reload: (id: string) => registry.reload(id, report),
535548
};
536549

@@ -559,12 +572,19 @@ async function boot(): Promise<BootReport | null> {
559572
if (tree) {
560573
injectImportMap(buildImportMapEntries({ ...record, files }, location.origin));
561574
}
575+
// Clear any live instance of a prior version transiently — a persisted
576+
// disable here would outlive the reinstall. registerLocal then swaps in
577+
// the new files.
578+
await registry.unload(id);
562579
registry.registerLocal({ metadata: record.metadata, files, mapped: tree });
563580
// The manifest is the row source for management UIs; mirror the boot
564581
// merge so a live install is visible without a restart.
565582
if (!manifest.modules.some((m) => m.identifier === id)) {
566583
manifest.modules.push({ ...record.metadata });
567584
}
585+
// Updating a module the user disabled installs the new files but must
586+
// not turn it back on: it stays off this session and every boot after.
587+
if (registry.isDisabled(id)) return { disabled: true };
568588
return registry.enable(id, report);
569589
};
570590
(modules as Record<string, unknown>).removeLocal = async (id: string) => {
@@ -578,6 +598,9 @@ async function boot(): Promise<BootReport | null> {
578598
// removal lands, but the running code only reverts on restart.
579599
if (plan === "requires-restart") {
580600
deleteLocalModule(id);
601+
// No staged copy is coming back, so a lingering disable would
602+
// silently skip the id if it is ever installed again.
603+
if (!stagedMeta.has(id)) registry.forgetDisabled(id);
581604
return { requiresRestart: true };
582605
}
583606
// A record the staged copy already shadowed: deleting it changes
@@ -588,6 +611,11 @@ async function boot(): Promise<BootReport | null> {
588611
if (!shadowedBy) registry.forgetDisabled(id);
589612
return shadowedBy ? { revertedTo: shadowedBy.version } : undefined;
590613
}
614+
// Whether the override was actually running decides whether the staged
615+
// copy should come up: reverting a module the user had disabled (or one
616+
// simply not loaded) must not start it, and for a theme that would also
617+
// unload the active theme and steal activeThemePref.
618+
const wasLoaded = registry.isLoaded(id);
591619
await registry.unload(id);
592620
deleteLocalModule(id);
593621
registry.unregisterLocal(id);
@@ -603,7 +631,7 @@ async function boot(): Promise<BootReport | null> {
603631
const at = manifest.modules.findIndex((m) => m.identifier === id);
604632
if (at >= 0) manifest.modules[at] = staged;
605633
else manifest.modules.push(staged);
606-
await registry.enable(id, report);
634+
if (wasLoaded && !registry.isDisabled(id)) await registry.enable(id, report);
607635
return { revertedTo: staged.version };
608636
}
609637
registry.forgetDisabled(id);

0 commit comments

Comments
 (0)