summaryrefslogtreecommitdiffhomepage
path: root/src/features/plugins/logic/cascade.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-09-03 18:05:29 +0900
committerAdam Malczewski <[email protected]>2026-09-03 18:05:29 +0900
commit3a4922a05930b0dc22740bf5621910803e8f275e (patch)
treed5f831c3d3838cfff346e378f5370a9c58bc2ed3 /src/features/plugins/logic/cascade.ts
parent33c7245f18346553f660434bc3ac589cff3e80c2 (diff)
downloaddispatch-web-3a4922a05930b0dc22740bf5621910803e8f275e.tar.gz
dispatch-web-3a4922a05930b0dc22740bf5621910803e8f275e.zip
feat(plugin-system): plan 07 frontend plugin manager ui
Diffstat (limited to 'src/features/plugins/logic/cascade.ts')
-rw-r--r--src/features/plugins/logic/cascade.ts42
1 files changed, 42 insertions, 0 deletions
diff --git a/src/features/plugins/logic/cascade.ts b/src/features/plugins/logic/cascade.ts
new file mode 100644
index 0000000..0cc6834
--- /dev/null
+++ b/src/features/plugins/logic/cascade.ts
@@ -0,0 +1,42 @@
+import type { PluginNode } from "./types";
+
+/**
+ * The transitive set of LOADED dependents that would also be unloaded if
+ * `targetId` were unloaded — the §8 cascade-uncheck warning's list ("unloading
+ * X also unchecks Y, Z"). Client mirror of the backend's
+ * `computeUnloadCascade` (plan 05): the reverse-dependency closure computed
+ * over the LOADED subgraph only (an unloaded intermediate breaks the chain —
+ * a loaded dependent behind it is already unmet and is not part of THIS
+ * cascade). The server still enforces the real cascade; the FE computes the
+ * same set to warn before the request goes out.
+ *
+ * Pure input → output; deterministic order (alphabetical by id) so tests and
+ * renderings are stable.
+ */
+export function computeCascade(targetId: string, nodes: readonly PluginNode[]): readonly string[] {
+ // Reverse edges within the loaded subgraph: dep id → loaded dependent ids.
+ const dependentsOf = new Map<string, string[]>();
+ for (const node of nodes) {
+ if (!node.loaded) continue;
+ for (const dep of node.dependsOn) {
+ const list = dependentsOf.get(dep);
+ if (list) list.push(node.id);
+ else dependentsOf.set(dep, [node.id]);
+ }
+ }
+
+ // BFS over reverse edges, collecting every reached loaded dependent.
+ const out: string[] = [];
+ const seen = new Set<string>([targetId]);
+ const queue = [targetId];
+ while (queue.length > 0) {
+ const current = queue.shift() as string;
+ for (const dependent of dependentsOf.get(current) ?? []) {
+ if (seen.has(dependent)) continue;
+ seen.add(dependent);
+ out.push(dependent);
+ queue.push(dependent);
+ }
+ }
+ return out.sort((a, b) => a.localeCompare(b));
+}