pi-weave 0.1.7 → 0.1.8
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.
- package/README.md +114 -34
- package/package.json +16 -4
- package/src/core/cache/workspace.ts +466 -0
- package/src/core/frontmatter.ts +217 -23
- package/src/core/git.ts +19 -0
- package/src/core/graph/build.ts +37 -4
- package/src/core/graph/current.ts +41 -28
- package/src/core/graph/mentions.ts +170 -0
- package/src/core/graph/model.ts +24 -0
- package/src/core/index.ts +12 -0
- package/src/core/openInEditor.ts +69 -0
- package/src/core/types.ts +40 -0
- package/src/core/vault.ts +477 -43
- package/src/core/view/cluster.ts +262 -0
- package/src/core/view/detail.ts +118 -0
- package/src/core/view/focus.ts +109 -0
- package/src/core/view/health.ts +156 -0
- package/src/core/view/index.ts +15 -0
- package/src/core/view/links.ts +105 -0
- package/src/core/view/time.ts +47 -0
- package/src/core/view/tree.ts +269 -0
- package/src/core/view/types.ts +39 -0
- package/src/pi/index.ts +104 -11
- package/src/pi/viewer/tui/explorer.ts +4 -2
- package/src/pi/viewer/tui/model.ts +46 -667
- package/src/pi/viewer/tui/openNote.ts +7 -56
- package/src/pi/viewer/tui/surface/explore.ts +4 -2
- package/src/pi/viewer/web/run.ts +331 -0
- package/src/web/client/api.dom.ts +40 -0
- package/src/web/client/api.ts +472 -0
- package/src/web/client/bootstrap.ts +58 -0
- package/src/web/client/context/context.model.ts +313 -0
- package/src/web/client/dist/app.js +751 -0
- package/src/web/client/graph/Graph.tsx +158 -0
- package/src/web/client/graph/column.model.ts +431 -0
- package/src/web/client/graph/graph.model.ts +538 -0
- package/src/web/client/graph/positions.ts +339 -0
- package/src/web/client/graph/project.ts +153 -0
- package/src/web/client/graph/renderer.dom.ts +52 -0
- package/src/web/client/graph/renderer.ts +279 -0
- package/src/web/client/graph/scheme.ts +44 -0
- package/src/web/client/live.model.ts +275 -0
- package/src/web/client/live.ts +151 -0
- package/src/web/client/main.tsx +27 -0
- package/src/web/client/note/Editor.tsx +102 -0
- package/src/web/client/note/Note.tsx +113 -0
- package/src/web/client/note/editor.controller.ts +151 -0
- package/src/web/client/note/editor.model.ts +636 -0
- package/src/web/client/note/note.model.ts +738 -0
- package/src/web/client/search/SearchPalette.tsx +105 -0
- package/src/web/client/search/search.model.ts +588 -0
- package/src/web/client/search/search.ts +107 -0
- package/src/web/client/shell/Columns.tsx +161 -0
- package/src/web/client/shell/ContextRail.tsx +87 -0
- package/src/web/client/shell/Divider.tsx +44 -0
- package/src/web/client/shell/FocusTrap.tsx +56 -0
- package/src/web/client/shell/Header.tsx +54 -0
- package/src/web/client/shell/HelpOverlay.tsx +70 -0
- package/src/web/client/shell/Shell.tsx +193 -0
- package/src/web/client/shell/StatusBar.tsx +28 -0
- package/src/web/client/shell/cssvars.ts +70 -0
- package/src/web/client/shell/drag.model.ts +170 -0
- package/src/web/client/shell/focus.model.ts +100 -0
- package/src/web/client/shell/keys.model.ts +453 -0
- package/src/web/client/shell/keys.ts +59 -0
- package/src/web/client/shell/layout.model.ts +526 -0
- package/src/web/client/shell/shell.model.ts +333 -0
- package/src/web/client/shell/theme.ts +477 -0
- package/src/web/client/shell/viewport.ts +29 -0
- package/src/web/client/state.ts +78 -0
- package/src/web/client/tree/Tree.tsx +138 -0
- package/src/web/client/tree/tree.model.ts +674 -0
- package/src/web/client/workspace.ts +214 -0
- package/src/web/server/page.ts +256 -0
- package/src/web/server/routes.ts +975 -0
- package/src/web/server/security.ts +361 -0
- package/src/web/server/server.ts +275 -0
- package/src/web/server/sse.ts +321 -0
- package/src/web/server/watcher.ts +507 -0
- package/src/web/shared/graph.ts +206 -0
- package/src/web/shared/layout.ts +497 -0
- package/src/web/shared/metrics.ts +136 -0
- package/src/web/shared/view.ts +200 -0
- package/src/web/shared/wire.ts +358 -0
package/src/core/graph/model.ts
CHANGED
|
@@ -60,4 +60,28 @@ export interface GraphModel {
|
|
|
60
60
|
staleness: StalenessReport | null;
|
|
61
61
|
nodes: GraphNode[];
|
|
62
62
|
edges: GraphEdge[];
|
|
63
|
+
/**
|
|
64
|
+
* slug → wiki-link targets that resolved to no note in the graph
|
|
65
|
+
* (weave-workspace §4.2).
|
|
66
|
+
*
|
|
67
|
+
* A `[[target]]` that matches nothing is not an error — it is Obsidian's
|
|
68
|
+
* ghost node, the affordance that offers to create the missing note. The
|
|
69
|
+
* builder used to count these and throw the names away, leaving
|
|
70
|
+
* `detail["dangling links"] = "3"` as the only trace; a display string is
|
|
71
|
+
* not something a UI can navigate.
|
|
72
|
+
*
|
|
73
|
+
* Lives here rather than on {@link GraphNode} because `detail` is
|
|
74
|
+
* display-only by contract and must not grow structure (§4.2 is explicit
|
|
75
|
+
* about preferring the model-level map). Notes with nothing unresolved are
|
|
76
|
+
* absent rather than present-and-empty, so the common case costs no bytes.
|
|
77
|
+
*
|
|
78
|
+
* Keyed by **slug**, not by node id: the consumers of this map (the note
|
|
79
|
+
* column, the wire payload's `dangling` field) speak slugs, and `note:` is
|
|
80
|
+
* a graph-internal prefix.
|
|
81
|
+
*
|
|
82
|
+
* Insertion order follows note order and each target list follows
|
|
83
|
+
* first-appearance order in the body, so the JSON stays byte-stable for
|
|
84
|
+
* unchanged inputs.
|
|
85
|
+
*/
|
|
86
|
+
danglingLinks: Record<string, string[]>;
|
|
63
87
|
}
|
package/src/core/index.ts
CHANGED
|
@@ -14,12 +14,24 @@ export * from "./vault";
|
|
|
14
14
|
export * from "./repoIndex";
|
|
15
15
|
export * from "./summaries";
|
|
16
16
|
export * from "./workspace";
|
|
17
|
+
export * from "./openInEditor";
|
|
17
18
|
export * from "./graph/model";
|
|
18
19
|
export * from "./graph/wikilinks";
|
|
20
|
+
export * from "./view";
|
|
19
21
|
export { buildGraph, dataTimestamp, DEFAULT_MAX_NOTES, type BuildGraphInput } from "./graph/build";
|
|
20
22
|
export {
|
|
21
23
|
buildCurrentGraph,
|
|
22
24
|
readNoteForView,
|
|
23
25
|
readOkfFileForView,
|
|
26
|
+
readRepositorySide,
|
|
24
27
|
type ViewNote,
|
|
25
28
|
} from "./graph/current";
|
|
29
|
+
export {
|
|
30
|
+
classifyPath,
|
|
31
|
+
DEFAULT_STALENESS_TTL_MS,
|
|
32
|
+
WorkspaceCache,
|
|
33
|
+
type CacheStats,
|
|
34
|
+
type InvalidationScope,
|
|
35
|
+
type WorkspaceCacheOptions,
|
|
36
|
+
type WorkspaceSnapshot,
|
|
37
|
+
} from "./cache/workspace";
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open a vault note in the user's editor.
|
|
3
|
+
*
|
|
4
|
+
* Originally `src/pi/viewer/tui/openNote.ts`, promoted into core because the
|
|
5
|
+
* browser workspace needs it too and `src/web/server/**` may not import
|
|
6
|
+
* `src/pi` (weave-workspace §2). It belongs here on the merits regardless:
|
|
7
|
+
* it is a pure fan-out over `core/vault` plus one `execFile`, with no
|
|
8
|
+
* harness and no terminal concept in it, and the same reasoning that put
|
|
9
|
+
* `readNoteForView` in core applies unchanged.
|
|
10
|
+
*
|
|
11
|
+
* The old path re-exports these two names, so no TUI file changed.
|
|
12
|
+
*
|
|
13
|
+
* Security: the slug is validated against the vault (traversal-safe) before
|
|
14
|
+
* any shell-out, and the command is invoked with `execFile`, not a shell, so
|
|
15
|
+
* neither the slug nor a `$EDITOR` containing metacharacters can compose a
|
|
16
|
+
* command. `$EDITOR` itself is trusted — it is the user's own configuration,
|
|
17
|
+
* and a hostile one already has whatever the user has.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { execFile } from "node:child_process";
|
|
21
|
+
import { platform } from "node:os";
|
|
22
|
+
import { promisify } from "node:util";
|
|
23
|
+
import { getNote, resolveNotePath } from "./vault";
|
|
24
|
+
|
|
25
|
+
const execFileAsync = promisify(execFile);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The OS command used to open a note file in the user's editor. Respects
|
|
29
|
+
* $EDITOR / $VISUAL (which may carry args, e.g. "code --wait"); falls back
|
|
30
|
+
* to the platform default opener. `env` and `os` are injectable so the
|
|
31
|
+
* mapping is unit-testable on any host without stubbing globals.
|
|
32
|
+
*/
|
|
33
|
+
export function openNoteCommand(
|
|
34
|
+
notePath: string,
|
|
35
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
36
|
+
os: NodeJS.Platform = platform(),
|
|
37
|
+
): { command: string; args: string[] } {
|
|
38
|
+
const editor = (env.EDITOR || env.VISUAL || "").trim();
|
|
39
|
+
if (editor) {
|
|
40
|
+
// editor is a trimmed non-empty string, so split+filter always yields a
|
|
41
|
+
// first segment — assert it rather than leave a dead `?? ""` branch.
|
|
42
|
+
const parts = editor.split(/\s+/).filter(Boolean);
|
|
43
|
+
const command = parts[0] as string;
|
|
44
|
+
return { command, args: [...parts.slice(1), notePath] };
|
|
45
|
+
}
|
|
46
|
+
if (os === "darwin") return { command: "open", args: [notePath] };
|
|
47
|
+
if (os === "win32") return { command: "cmd", args: ["/c", "start", "", notePath] };
|
|
48
|
+
return { command: "xdg-open", args: [notePath] };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Open a note in the OS editor. The slug is validated against the vault
|
|
53
|
+
* (traversal-safe) before any shell-out; returns false when the note does
|
|
54
|
+
* not exist or the slug is unsafe.
|
|
55
|
+
*/
|
|
56
|
+
export async function openNoteInEditor(
|
|
57
|
+
vaultRoot: string,
|
|
58
|
+
slug: string,
|
|
59
|
+
openCommand: (notePath: string) => { command: string; args: string[] } = openNoteCommand,
|
|
60
|
+
): Promise<boolean> {
|
|
61
|
+
const path = resolveNotePath(vaultRoot, slug);
|
|
62
|
+
if (path === null) return false; // traversal-safe
|
|
63
|
+
const note = await getNote(vaultRoot, slug);
|
|
64
|
+
if (note === null) return false;
|
|
65
|
+
const { command, args } = openCommand(path);
|
|
66
|
+
if (!command) return false;
|
|
67
|
+
await execFileAsync(command, args);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
package/src/core/types.ts
CHANGED
|
@@ -22,11 +22,51 @@ export interface NoteMeta {
|
|
|
22
22
|
source: NoteSource;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* The verbatim lines of a note's front-matter block, in file order, without
|
|
27
|
+
* the enclosing `---` fences.
|
|
28
|
+
*
|
|
29
|
+
* This is how pi-weave keeps front matter **lossless**. `NoteMeta` names the
|
|
30
|
+
* five fields the engine understands; a vault is a directory of files a human
|
|
31
|
+
* also edits, so real notes carry keys the engine has never heard of —
|
|
32
|
+
* Obsidian's `aliases`, `cssclass`, `publish`, whatever the user invented last
|
|
33
|
+
* Tuesday. Modelling those as parsed values would mean re-emitting them, and
|
|
34
|
+
* re-emitting a value the parser only half-understands is how formatting (and
|
|
35
|
+
* then content) gets destroyed.
|
|
36
|
+
*
|
|
37
|
+
* So they are not parsed at all: the raw lines are carried through, and
|
|
38
|
+
* `serializeNote` replays them, substituting a freshly rendered line only for
|
|
39
|
+
* the keys `NoteMeta` actually owns. Unknown keys — including ones using
|
|
40
|
+
* syntax the subset parser cannot represent, such as block lists or folded
|
|
41
|
+
* scalars — come back out byte-identical, in their original order.
|
|
42
|
+
*
|
|
43
|
+
* Deliberately `readonly string[]` rather than a parsed record: a second
|
|
44
|
+
* representation of a line is a second thing that can disagree with the file.
|
|
45
|
+
* The key of a line is derived with the same rule the parser uses, at the one
|
|
46
|
+
* place that needs it.
|
|
47
|
+
*/
|
|
48
|
+
export type NoteFrontMatter = readonly string[];
|
|
49
|
+
|
|
25
50
|
/** A vault note: metadata plus its Markdown body. */
|
|
26
51
|
export interface Note extends NoteMeta {
|
|
27
52
|
/** File-name slug (no extension). Stable identity of the note. */
|
|
28
53
|
slug: string;
|
|
29
54
|
body: string;
|
|
55
|
+
/**
|
|
56
|
+
* The note's front-matter block as it appeared on disk, so a read → write
|
|
57
|
+
* round trip preserves keys outside {@link NoteMeta} (see
|
|
58
|
+
* {@link NoteFrontMatter}).
|
|
59
|
+
*
|
|
60
|
+
* Optional because a `Note` need not have come from a file: graph fixtures
|
|
61
|
+
* and in-memory projections construct one directly. Absent means "no layout
|
|
62
|
+
* to preserve", and `serializeNote` then emits the canonical five fields.
|
|
63
|
+
*
|
|
64
|
+
* Not carried on {@link NoteSummary}, and deliberately not on the wire: the
|
|
65
|
+
* browser never needs to see a user's unknown keys in order for them to
|
|
66
|
+
* survive, because every write path re-reads the file it is about to
|
|
67
|
+
* overwrite.
|
|
68
|
+
*/
|
|
69
|
+
frontMatter?: NoteFrontMatter;
|
|
30
70
|
}
|
|
31
71
|
|
|
32
72
|
/** Summary of one note for list/search output. */
|