pi-weave 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -545,9 +545,10 @@ export interface ViewBox {
545
545
  * session: the node stays under the cursor, the canvas bounds the drag, and
546
546
  * `[fit]` re-frames onto whatever the current positions are.
547
547
  *
548
- * The box is the exact extent of the rendered positions — no padding of its
549
- * own, because sigma's `stagePadding` (in pixels) already insets the fit, and
550
- * the same box handed back on `fit()` re-frames dragged-apart graphs.
548
+ * Use the position extent, with a minimum span of four collision radii.
549
+ * A single node (or coincident nodes) has no extent; Sigma scales layout-unit
550
+ * node sizes by that span, so a near-zero box magnifies one node across the
551
+ * canvas. Pixel-based `stagePadding` cannot prevent that.
551
552
  *
552
553
  * `null` for an empty graph: there is nothing to frame, and the caller
553
554
  * clears the override so sigma falls back to its own behaviour.
@@ -567,7 +568,8 @@ export function frameBox(points: readonly Point[]): ViewBox | null {
567
568
  if (p.y > maxY) maxY = p.y;
568
569
  }
569
570
  if (seen === 0) return null;
570
- return { x: [minX, maxX], y: [minY, maxY] };
571
+ const padding = Math.max(0, (COLLIDE_RADIUS * 4 - Math.max(maxX - minX, maxY - minY)) / 2);
572
+ return { x: [minX - padding, maxX + padding], y: [minY - padding, maxY + padding] };
571
573
  }
572
574
 
573
575
  /** The empty graph. What the column renders before the first payload lands. */
@@ -0,0 +1,79 @@
1
+ import { platform as osPlatform } from "node:os";
2
+ import { resolveVaultRoot, WorkspaceCache } from "../../core";
3
+ import {
4
+ startWorkspaceServer,
5
+ type StartWorkspaceServerOptions,
6
+ type WorkspaceServer,
7
+ } from "./server";
8
+
9
+ export type StartServerFn = (opts: StartWorkspaceServerOptions) => Promise<WorkspaceServer>;
10
+
11
+ export interface WorkspaceServerSession {
12
+ server: WorkspaceServer;
13
+ cache: WorkspaceCache;
14
+ cwd: string;
15
+ }
16
+
17
+ export interface WorkspaceServerControllerDeps {
18
+ startServer?: StartServerFn;
19
+ vaultRoot?: () => string;
20
+ onStateChange?: () => void;
21
+ }
22
+
23
+ /** Owns one browser-workspace server for one plugin instance. */
24
+ export class WorkspaceServerController {
25
+ private session: WorkspaceServerSession | null = null;
26
+ private pending: Promise<void> = Promise.resolve();
27
+
28
+ constructor(private readonly deps: WorkspaceServerControllerDeps = {}) {}
29
+
30
+ port(): number | null {
31
+ return this.session?.server.port ?? null;
32
+ }
33
+
34
+ run(cwd: string): Promise<{ session: WorkspaceServerSession; started: boolean }> {
35
+ return this.enqueue(async () => {
36
+ const existing = this.session;
37
+ if (existing?.cwd === cwd) return { session: existing, started: false };
38
+ if (existing) await this.stop();
39
+ const session = await this.bootOnce(cwd);
40
+ this.session = session;
41
+ this.deps.onStateChange?.();
42
+ return { session, started: true };
43
+ });
44
+ }
45
+
46
+ close(): Promise<void> {
47
+ return this.enqueue(() => this.stop());
48
+ }
49
+
50
+ private enqueue<T>(task: () => Promise<T>): Promise<T> {
51
+ const result = this.pending.then(task);
52
+ this.pending = result.then(() => undefined, () => undefined);
53
+ return result;
54
+ }
55
+
56
+ private async stop(): Promise<void> {
57
+ const session = this.session;
58
+ if (!session) return;
59
+ this.session = null;
60
+ await session.server.close();
61
+ this.deps.onStateChange?.();
62
+ }
63
+
64
+ private async bootOnce(cwd: string): Promise<WorkspaceServerSession> {
65
+ const vaultRoot = (this.deps.vaultRoot ?? resolveVaultRoot)();
66
+ const cache = new WorkspaceCache({ cwd, vaultRoot });
67
+ const server = await (this.deps.startServer ?? startWorkspaceServer)({ cwd, vaultRoot, cache });
68
+ return { server, cache, cwd };
69
+ }
70
+ }
71
+
72
+ export function browserOpenCommand(
73
+ url: string,
74
+ os: NodeJS.Platform = osPlatform(),
75
+ ): { command: string; args: string[] } {
76
+ if (os === "darwin") return { command: "open", args: [url] };
77
+ if (os === "win32") return { command: "cmd", args: ["/c", "start", "", url] };
78
+ return { command: "xdg-open", args: [url] };
79
+ }