pi-weave 0.1.18 → 0.1.19
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/package.json +1 -1
- package/src/core/frontmatter.ts +19 -3
- package/src/core/graph/build.ts +8 -1
- package/src/core/index.ts +1 -0
- package/src/core/vault.ts +26 -0
- package/src/web/client/api.ts +1 -0
- package/src/web/client/dist/app.js +31 -31
- package/src/web/client/tree/Tree.tsx +40 -12
- package/src/web/client/tree/tree.model.ts +6 -4
- package/src/web/server/routes.ts +6 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-weave",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "An agent-native knowledge workspace for your life and your code. Smart notepad + repository exploration, readable by humans and agents alike.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
package/src/core/frontmatter.ts
CHANGED
|
@@ -242,12 +242,19 @@ function canonicalBlock(meta: NoteMeta): string[] {
|
|
|
242
242
|
function replayBlock(meta: NoteMeta, frontMatter: NoteFrontMatter): string[] {
|
|
243
243
|
/** Top-level keys the file declares, in any syntax — including frozen ones. */
|
|
244
244
|
const declared = new Set<string>();
|
|
245
|
+
/** Values of declared scalar keys, unquoted, to detect defaulted fallbacks. */
|
|
246
|
+
const declaredValues = new Map<string, string>();
|
|
245
247
|
/** Owned keys already re-rendered, so a duplicate key collapses to one line. */
|
|
246
248
|
const rendered = new Set<string>();
|
|
247
249
|
const out: string[] = [];
|
|
248
250
|
|
|
249
251
|
for (const line of scanFrontMatter(frontMatter)) {
|
|
250
|
-
if (line.key !== null)
|
|
252
|
+
if (line.key !== null) {
|
|
253
|
+
declared.add(line.key);
|
|
254
|
+
if (line.scalar) {
|
|
255
|
+
declaredValues.set(line.key, unquoteField(line.text.slice(line.text.indexOf(":") + 1).trim()));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
251
258
|
if (line.key === null || !line.scalar || !MANAGED.has(line.key)) {
|
|
252
259
|
out.push(line.text); // carried verbatim — never interpreted, never reformatted
|
|
253
260
|
continue;
|
|
@@ -260,8 +267,13 @@ function replayBlock(meta: NoteMeta, frontMatter: NoteFrontMatter): string[] {
|
|
|
260
267
|
out.push(renderManaged(line.key as ManagedFrontMatterKey, meta));
|
|
261
268
|
}
|
|
262
269
|
|
|
270
|
+
const dateVal = declaredValues.get("date");
|
|
271
|
+
const createdVal = declaredValues.get("created");
|
|
263
272
|
for (const key of MANAGED_FRONT_MATTER_KEYS) {
|
|
264
273
|
if (declared.has(key) || isDefaulted(key, meta)) continue;
|
|
274
|
+
if (key === "created" && (!declared.has("created") && !declared.has("date"))) continue;
|
|
275
|
+
if (key === "created" && dateVal !== undefined && meta.created === dateVal) continue;
|
|
276
|
+
if (key === "updated" && ((dateVal !== undefined && meta.updated === dateVal) || (createdVal !== undefined && meta.updated === createdVal))) continue;
|
|
265
277
|
out.push(renderManaged(key, meta));
|
|
266
278
|
}
|
|
267
279
|
return out;
|
|
@@ -302,10 +314,14 @@ export function parseNoteFile(text: string): ParsedNoteFile {
|
|
|
302
314
|
if (!title) {
|
|
303
315
|
throw new Error("Front matter is missing required field: title");
|
|
304
316
|
}
|
|
317
|
+
const createdRaw = parsed.fields.get("created") ?? parsed.fields.get("date");
|
|
318
|
+
const created = createdRaw ? unquoteField(createdRaw) : "";
|
|
319
|
+
const updatedRaw = parsed.fields.get("updated") ?? parsed.fields.get("date") ?? created;
|
|
320
|
+
const updated = updatedRaw ? unquoteField(updatedRaw) : "";
|
|
305
321
|
const meta: NoteMeta = {
|
|
306
322
|
title: unquoteField(title),
|
|
307
|
-
created
|
|
308
|
-
updated
|
|
323
|
+
created,
|
|
324
|
+
updated,
|
|
309
325
|
tags: parseTags(parsed.fields.get("tags") ?? "[]"),
|
|
310
326
|
source: parseSource(parsed.fields.get("source") ?? "human"),
|
|
311
327
|
};
|
package/src/core/graph/build.ts
CHANGED
|
@@ -160,15 +160,22 @@ function buildVaultSide(
|
|
|
160
160
|
.filter((d) => d.length > 0)
|
|
161
161
|
.sort();
|
|
162
162
|
const notesIn = (dir: string): number => kept.filter((n) => n.slug.startsWith(`${dir}/`)).length;
|
|
163
|
+
const totalNotesIn = (dir: string): number => input.notes.filter((n) => n.slug.startsWith(`${dir}/`)).length;
|
|
163
164
|
for (const dir of noteDirs) {
|
|
164
165
|
const id = `vfolder:${dir}`;
|
|
165
166
|
folderIds.set(dir, id);
|
|
167
|
+
const keptCount = notesIn(dir);
|
|
168
|
+
const totalCount = totalNotesIn(dir);
|
|
169
|
+
const folderDetail: Record<string, string> = { path: dir, notes: String(keptCount) };
|
|
170
|
+
if (totalCount > keptCount) {
|
|
171
|
+
folderDetail.warning = `${totalCount - keptCount} older note(s) in this folder omitted by note limit`;
|
|
172
|
+
}
|
|
166
173
|
nodes.push({
|
|
167
174
|
id,
|
|
168
175
|
kind: "module",
|
|
169
176
|
label: dir.split("/").pop() ?? dir,
|
|
170
177
|
provenance: null,
|
|
171
|
-
detail:
|
|
178
|
+
detail: folderDetail,
|
|
172
179
|
});
|
|
173
180
|
const parentDir = dir.split("/").slice(0, -1).join("/");
|
|
174
181
|
const parent = folderIds.get(parentDir) ?? "vault";
|
package/src/core/index.ts
CHANGED
package/src/core/vault.ts
CHANGED
|
@@ -129,6 +129,14 @@ export async function getNote(root: string, slug: string): Promise<Note | null>
|
|
|
129
129
|
}
|
|
130
130
|
try {
|
|
131
131
|
const { meta, body, frontMatter } = parseNoteFile(text);
|
|
132
|
+
if (!meta.updated || !meta.created) {
|
|
133
|
+
const st = await fs.stat(path).catch(() => null);
|
|
134
|
+
if (st) {
|
|
135
|
+
const mtime = st.mtime.toISOString();
|
|
136
|
+
if (!meta.updated) meta.updated = mtime;
|
|
137
|
+
if (!meta.created) meta.created = mtime;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
132
140
|
return { slug, ...meta, body, frontMatter };
|
|
133
141
|
} catch {
|
|
134
142
|
return null;
|
|
@@ -438,6 +446,24 @@ export async function deleteFolder(root: string, folder: string): Promise<VaultM
|
|
|
438
446
|
});
|
|
439
447
|
}
|
|
440
448
|
|
|
449
|
+
/** Create a new vault folder. */
|
|
450
|
+
export async function createFolder(root: string, folder: string): Promise<VaultMutationResult> {
|
|
451
|
+
const trimmed = folder.trim();
|
|
452
|
+
if (trimmed === "") return { ok: false, reason: "invalid" };
|
|
453
|
+
const rawParts = trimmed.split("/").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
454
|
+
if (rawParts.length === 0 || rawParts.some((p) => p === "." || p === "..")) return { ok: false, reason: "invalid" };
|
|
455
|
+
const parts = rawParts.map(slugify).filter((p) => p.length > 0);
|
|
456
|
+
if (parts.length === 0) return { ok: false, reason: "invalid" };
|
|
457
|
+
const target = parts.join("/");
|
|
458
|
+
const to = resolveFolderPath(root, target);
|
|
459
|
+
if (to === null) return { ok: false, reason: "invalid" };
|
|
460
|
+
return withVaultLock(root, async () => {
|
|
461
|
+
if (await exists(to)) return { ok: false, reason: "collision" };
|
|
462
|
+
await fs.mkdir(to, { recursive: true });
|
|
463
|
+
return { ok: true, path: target };
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
441
467
|
async function listVaultEntries(root: string): Promise<{ files: string[]; folders: string[] }> {
|
|
442
468
|
const dir = join(root, NOTES_DIR);
|
|
443
469
|
const files: string[] = [];
|
package/src/web/client/api.ts
CHANGED
|
@@ -326,5 +326,6 @@ const folderUrl = (path: string, suffix = ""): string => `/api/folder/${path.spl
|
|
|
326
326
|
export const renameNote = (fetchImpl: FetchLike, slug: string, name: string) => mutation(fetchImpl, noteUrl(slug, "/rename"), "POST", { name });
|
|
327
327
|
export const moveNote = (fetchImpl: FetchLike, slug: string, folder: string | null) => mutation(fetchImpl, noteUrl(slug, "/move"), "POST", { folder });
|
|
328
328
|
export const deleteNote = (fetchImpl: FetchLike, slug: string) => mutation(fetchImpl, noteUrl(slug), "DELETE");
|
|
329
|
+
export const createFolder = (fetchImpl: FetchLike, path: string) => mutation(fetchImpl, folderUrl(path), "POST");
|
|
329
330
|
export const renameFolder = (fetchImpl: FetchLike, path: string, name: string) => mutation(fetchImpl, folderUrl(path, "/rename"), "POST", { name });
|
|
330
331
|
export const deleteFolder = (fetchImpl: FetchLike, path: string) => mutation(fetchImpl, folderUrl(path), "DELETE");
|