pi-weave 0.2.0 → 0.2.1
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 +7 -3
- package/package.json +1 -1
- package/src/core/index.ts +1 -0
- package/src/core/links/repair.ts +2 -2
- package/src/core/vault.ts +46 -1
- package/src/web/client/api.ts +6 -0
- package/src/web/client/dist/app.js +107 -83
- package/src/web/client/note/Note.tsx +139 -14
- package/src/web/client/note/note.model.ts +51 -0
- package/src/web/client/shell/Columns.tsx +12 -1
- package/src/web/client/shell/Shell.tsx +56 -5
- package/src/web/client/shell/theme.ts +24 -0
- package/src/web/client/tree/Tree.tsx +11 -1
- package/src/web/server/routes.ts +18 -6
package/README.md
CHANGED
|
@@ -147,15 +147,19 @@ Renaming or moving a note rewrites its inbound links automatically, so its backl
|
|
|
147
147
|
The browser workspace has four connected views:
|
|
148
148
|
|
|
149
149
|
- **Tree** — notes, folders, and repository structure, with text and provenance filters.
|
|
150
|
-
- **Note** — rendered Markdown with clickable `[[wikilinks]]`, link previews, tags, authorship, and an action to open the
|
|
151
|
-
`$EDITOR`.
|
|
150
|
+
- **Note** — rendered Markdown with clickable `[[wikilinks]]`, link previews, tags, authorship, click-to-edit, and an action to open the
|
|
151
|
+
source in `$EDITOR`.
|
|
152
152
|
- **Graph** — a navigable map of notes, links, mentions, modules, and repository relationships. Selecting something updates every view.
|
|
153
153
|
- **Context** — links, backlinks, tags, and code mentions for the current selection.
|
|
154
154
|
|
|
155
155
|
Search with `⌘K` / `Ctrl K`. Press `?` for all shortcuts. The workspace updates as notes change on disk, so a note written by Pi appears
|
|
156
156
|
without a reload. It follows the system theme by default and can be switched between light and dark.
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
**Click the note text to edit it** — the prose swaps for its Markdown source; `⌘S` / `Ctrl S` saves, `Esc` or **Done** closes. Selecting
|
|
159
|
+
text to copy and clicking a `[[wikilink]]` do what they always did; neither opens the editor. A save rewrites the body and nothing
|
|
160
|
+
else — unknown front-matter fields, key order, and the append-only `## Raw` tail of a dictated note all survive untouched, so a note edited
|
|
161
|
+
here and a note edited in Obsidian stay byte-compatible. Editing with `$EDITOR`, Obsidian, or the `weave_note` tool works as before; the
|
|
162
|
+
last writer wins, and the workspace picks up outside changes within a couple of seconds.
|
|
159
163
|
|
|
160
164
|
`/weave-view tui` is the smaller, read-only terminal explorer: tree, focused neighborhood, details, and link health over the same graph.
|
|
161
165
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-weave",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
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/index.ts
CHANGED
package/src/core/links/repair.ts
CHANGED
|
@@ -99,7 +99,7 @@ const RAW_HEADING_RE = new RegExp(`^${RAW_NOTES_HEADING}[ \\t]*\\r?$`, "m");
|
|
|
99
99
|
* inside a ```` block would too — and each mistake ends the protected region
|
|
100
100
|
* early, exposing example links in documentation to rewriting.
|
|
101
101
|
*/
|
|
102
|
-
function fenceRanges(body: string): [number, number][] {
|
|
102
|
+
export function fenceRanges(body: string): [number, number][] {
|
|
103
103
|
const ranges: [number, number][] = [];
|
|
104
104
|
let open: { at: number; marker: string; length: number } | null = null;
|
|
105
105
|
for (const match of body.matchAll(FENCE_RE)) {
|
|
@@ -134,7 +134,7 @@ function fenceRanges(body: string): [number, number][] {
|
|
|
134
134
|
* refuse to repair — every link below it. Matching a whole heading line also
|
|
135
135
|
* keeps `## Rawhide` from starting a tail.
|
|
136
136
|
*/
|
|
137
|
-
function rawTailStart(body: string, fences: readonly [number, number][]): number {
|
|
137
|
+
export function rawTailStart(body: string, fences: readonly [number, number][]): number {
|
|
138
138
|
let from = 0;
|
|
139
139
|
for (;;) {
|
|
140
140
|
const rest = body.slice(from);
|
package/src/core/vault.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
unquoteField,
|
|
11
11
|
upsertFrontMatterFields,
|
|
12
12
|
} from "./frontmatter";
|
|
13
|
-
import { auditLinks, rewriteLinks, RAW_NOTES_HEADING, type LinkAudit, type LinkFix } from "./links/repair";
|
|
13
|
+
import { auditLinks, fenceRanges, rawTailStart, rewriteLinks, RAW_NOTES_HEADING, type LinkAudit, type LinkFix } from "./links/repair";
|
|
14
14
|
import { withMutationQueue } from "./mutex";
|
|
15
15
|
import { NOTES_DIR, OKF_MANIFEST } from "./paths";
|
|
16
16
|
import { slugify, uniqueSlug } from "./slug";
|
|
@@ -596,6 +596,51 @@ export async function repairVaultLinks(root: string, options: { apply?: boolean
|
|
|
596
596
|
});
|
|
597
597
|
}
|
|
598
598
|
|
|
599
|
+
/**
|
|
600
|
+
* Keep the on-disk `## Raw` tail whatever the body says — it is append-only.
|
|
601
|
+
* `rawTailStart`, not `extractRawTail`: fence-aware, for untrusted input.
|
|
602
|
+
*/
|
|
603
|
+
function preserveRawTail(currentBody: string, nextBody: string): string {
|
|
604
|
+
const tail = currentBody.slice(tailBoundary(currentBody)).trimEnd();
|
|
605
|
+
const head = nextBody.slice(0, tailBoundary(nextBody)).trim();
|
|
606
|
+
if (tail === "") return head;
|
|
607
|
+
// An emptied head leaves the tail alone, not a leading blank line.
|
|
608
|
+
return head === "" ? tail : `${head}\n\n${tail}`;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** A `---` rule, alone on the line, with only blank space after it. */
|
|
612
|
+
const TAIL_RULE_RE = /(?:^|\n)-{3,}[ \t]*\r?\n\s*$/;
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Where the tail's text begins — the `---` rule, not the heading
|
|
616
|
+
* `rawTailStart` returns. Splitting at the heading orphans the rule.
|
|
617
|
+
*/
|
|
618
|
+
function tailBoundary(body: string): number {
|
|
619
|
+
const heading = rawTailStart(body, fenceRanges(body));
|
|
620
|
+
if (heading === body.length) return heading;
|
|
621
|
+
const rule = TAIL_RULE_RE.exec(body.slice(0, heading));
|
|
622
|
+
if (rule === null) return heading;
|
|
623
|
+
// `index` is the newline before the rule when there is one; step over it.
|
|
624
|
+
return rule.index === 0 && !body.startsWith("\n") ? 0 : rule.index + 1;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Replace a note's Markdown body; unknown front matter rides on
|
|
629
|
+
* `note.frontMatter` and only `updated` moves. Last write wins — no
|
|
630
|
+
* `expectedRevision`; docs/weave-workspace.md §11 P5.3 argues the trade.
|
|
631
|
+
*/
|
|
632
|
+
export async function setNoteBody(root: string, slug: string, body: string, now = new Date()): Promise<VaultMutationResult> {
|
|
633
|
+
const path = resolveNotePath(root, slug);
|
|
634
|
+
if (path === null) return { ok: false, reason: "invalid" };
|
|
635
|
+
return withVaultLock(root, async () => {
|
|
636
|
+
const note = await getNote(root, slug);
|
|
637
|
+
if (note === null) return { ok: false, reason: "missing" };
|
|
638
|
+
const next = preserveRawTail(note.body, body);
|
|
639
|
+
await writeNote(path, slug, { ...note, updated: now.toISOString() }, next, note.frontMatter);
|
|
640
|
+
return { ok: true, slug };
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
|
|
599
644
|
/** Rename a note in place and keep its front-matter title in sync. */
|
|
600
645
|
export async function renameNote(root: string, slug: string, name: string, now = new Date()): Promise<VaultMutationResult> {
|
|
601
646
|
const from = resolveNotePath(root, slug);
|
package/src/web/client/api.ts
CHANGED
|
@@ -323,6 +323,12 @@ function mutation(fetchImpl: FetchLike, url: string, method: string, body?: unkn
|
|
|
323
323
|
const noteUrl = (slug: string, suffix = ""): string => `/api/note/${encodeURIComponent(slug)}${suffix}`;
|
|
324
324
|
const folderUrl = (path: string, suffix = ""): string => `/api/folder/${path.split("/").map(encodeURIComponent).join("/")}${suffix}`;
|
|
325
325
|
|
|
326
|
+
/**
|
|
327
|
+
* `POST /api/note/:slug` — save the note's Markdown body. No revision
|
|
328
|
+
* (last-write-wins, see core's `setNoteBody`), and a `MutationResult` rather
|
|
329
|
+
* than the note: the poll is what puts the new text back on screen.
|
|
330
|
+
*/
|
|
331
|
+
export const saveNote = (fetchImpl: FetchLike, slug: string, body: string) => mutation(fetchImpl, noteUrl(slug), "POST", { body });
|
|
326
332
|
export const renameNote = (fetchImpl: FetchLike, slug: string, name: string) => mutation(fetchImpl, noteUrl(slug, "/rename"), "POST", { name });
|
|
327
333
|
export const moveNote = (fetchImpl: FetchLike, slug: string, folder: string | null) => mutation(fetchImpl, noteUrl(slug, "/move"), "POST", { folder });
|
|
328
334
|
export const deleteNote = (fetchImpl: FetchLike, slug: string) => mutation(fetchImpl, noteUrl(slug), "DELETE");
|