@tanishqkancharla/diffmap 0.4.0 → 0.6.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.
- package/README.md +76 -2
- package/package.json +1 -1
- package/src/annotations.ts +17 -1
- package/src/extractDocument.ts +10 -2
- package/src/host/home.md +82 -83
- package/src/host/home.test.ts +0 -1
- package/src/parseFence.ts +81 -12
- package/src/parseViewer.test.ts +147 -4
- package/src/parseViewer.ts +44 -9
- package/src/serve.test.ts +22 -1
- package/src/services.ts +120 -13
- package/src/viewer/CallStackDiff.tsx +30 -32
- package/src/viewer/ChangesCard.tsx +0 -2
- package/src/viewer/CodeBlock.tsx +79 -6
- package/src/viewer/ComarkView.tsx +34 -0
- package/src/viewer/Fence.tsx +4 -0
- package/src/viewer/HtmlBlock.tsx +2 -1
- package/src/viewer/MermaidBlock.tsx +55 -14
- package/src/viewer/ServiceDiagramBlock.tsx +87 -52
- package/src/viewer/ServiceStage.tsx +198 -57
- package/src/viewer/SourceDiffPanel.tsx +138 -42
- package/src/viewer/TableOfContents.tsx +44 -18
- package/src/viewer/TocButton.tsx +28 -0
- package/src/viewer/ViewerApp.tsx +10 -0
- package/src/viewer/diagramDiff.ts +69 -0
- package/src/viewer/diagramLinks.ts +54 -6
- package/src/viewer/pierre.ts +20 -2
- package/src/viewer/serviceLayout.ts +16 -5
- package/vite.config.ts +4 -0
package/README.md
CHANGED
|
@@ -76,13 +76,14 @@ import {
|
|
|
76
76
|
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
77
77
|
| `mermaid` | Beautiful Mermaid |
|
|
78
78
|
| `services` | Animated service diagram you can play or step through |
|
|
79
|
+
| `mermaid diff` or `services diff` | The same diagram with [added and removed parts marked](#diagram-diffs) |
|
|
79
80
|
| `callstack` or `diff` containing `└──` / `├──` | Interactive stack rows, no file header |
|
|
80
81
|
| `diff` or `diff:path` with a file path | Pierre patch with Pierre’s file header |
|
|
81
82
|
| `diff` with no path | Pierre patch, no file header |
|
|
82
83
|
| `changes` with a commit, range, or empty body | Files changed card with a Review button; empty means uncommitted changes |
|
|
83
84
|
| `start:end:path` | Pierre file excerpt with Pierre’s file header |
|
|
84
85
|
| `html` | Trusted HTML from this file. diffmap does not sanitize it. Only use it for local files you wrote. |
|
|
85
|
-
| other langs | Highlighted code with optional [
|
|
86
|
+
| other langs | Highlighted code with optional [titles, highlights, and notes](#highlight-lines-and-add-notes) |
|
|
86
87
|
|
|
87
88
|
Fences keep whitespace. Use them for mermaid, call stacks, and diffs.
|
|
88
89
|
|
|
@@ -110,6 +111,13 @@ function requestHandler(record: RecordInput) {
|
|
|
110
111
|
- A symbol that does not appear, overlapping line notes, or a line number
|
|
111
112
|
outside the fence is a parse error. A note line above other code stays code.
|
|
112
113
|
|
|
114
|
+
Add `[Title]` after the language to show a title above the code, as in
|
|
115
|
+
` ```ts [src/request.ts] {2-3} `. Call stacks and `mermaid` fences take a
|
|
116
|
+
title the same way, as in ` ```mermaid diff [Request flow] `, or
|
|
117
|
+
`::callstack{title="Request flow"}` and `::mermaid{title="Request flow"}` in
|
|
118
|
+
MDC. A `services` fence uses its [start caption](#animated-service-diagrams)
|
|
119
|
+
instead.
|
|
120
|
+
|
|
113
121
|
Call stacks take the same highlights and notes, counting rows from the first.
|
|
114
122
|
Write their notes with `#`. A symbol note matches the row text you see, not
|
|
115
123
|
hidden references. Inside a linked row, symbol notes show on hover only; the
|
|
@@ -146,8 +154,13 @@ startServer
|
|
|
146
154
|
::html
|
|
147
155
|
<aside>Note.</aside>
|
|
148
156
|
::
|
|
157
|
+
|
|
158
|
+
::details{summary="How it works"}
|
|
159
|
+
Folded content: prose, lists, and any fence above.
|
|
160
|
+
::
|
|
149
161
|
```
|
|
150
162
|
|
|
163
|
+
`::details` folds its content under the `summary` label until the reader opens it.
|
|
151
164
|
`.md` and `.mdx` are both markdown. Curly braces in prose are plain text.
|
|
152
165
|
|
|
153
166
|
## Link call stacks to source changes
|
|
@@ -244,6 +257,8 @@ service API "HTTP server" [[src/api.ts]]
|
|
|
244
257
|
state attempts = 0
|
|
245
258
|
> Failures since the last success. Five locks the account.
|
|
246
259
|
|
|
260
|
+
start "Alice logs in"
|
|
261
|
+
> She is signed out.
|
|
247
262
|
step "The browser submits the form"
|
|
248
263
|
Browser -> API: POST /login
|
|
249
264
|
step "The API asks Auth to verify"
|
|
@@ -261,6 +276,11 @@ step "The API sets the session cookie"
|
|
|
261
276
|
another to nest it. Names are unique within the diagram.
|
|
262
277
|
- `state <key> = <value>` declares a value and its initial state. Indent it under
|
|
263
278
|
its service.
|
|
279
|
+
- `start "caption"` at column 0, before the first step, captions the whole
|
|
280
|
+
diagram, such as `start "Alice logs in"`. It shows above the diagram until
|
|
281
|
+
the reader steps, and each step's caption replaces it. A `>` line under it
|
|
282
|
+
can say what the initial state shows. A `services` fence takes no `[Title]`;
|
|
283
|
+
the start caption is its title.
|
|
264
284
|
- `step "caption"` starts a step at column 0. The caption is required: a
|
|
265
285
|
sentence that says what happens, shown under the controls. Indented lines
|
|
266
286
|
under it are `From -> To: label` events and `Service.key = value` updates.
|
|
@@ -270,7 +290,7 @@ step "The API sets the session cookie"
|
|
|
270
290
|
consecutive `>` lines join.
|
|
271
291
|
- Under a service or state, it is a description: the name gets a dotted
|
|
272
292
|
underline and the text shows in a tooltip on hover or focus.
|
|
273
|
-
- Under a step, it explains the
|
|
293
|
+
- Under `start` or a step, it explains the caption and shows under it.
|
|
274
294
|
- Under an event or update, it is a note for that step. Notes appear in a
|
|
275
295
|
column beside the diagram once the step's events land, each joined by a
|
|
276
296
|
line to its event label or state row.
|
|
@@ -284,6 +304,60 @@ connection between each pair of services that talk. A request and its reply
|
|
|
284
304
|
share that connection. Indentation is significant, so `services` has no MDC form.
|
|
285
305
|
See [the runnable examples](fixtures/services.md).
|
|
286
306
|
|
|
307
|
+
## Diagram diffs
|
|
308
|
+
|
|
309
|
+
Add `diff` after `mermaid` or `services` to show a change in one diagram.
|
|
310
|
+
Start every line with `-` for Today only, `+` for Proposed only, or a space
|
|
311
|
+
for both, as in a call stack diff. Indentation starts after that column.
|
|
312
|
+
|
|
313
|
+
````md
|
|
314
|
+
```mermaid diff
|
|
315
|
+
flowchart LR
|
|
316
|
+
Client --> Handler
|
|
317
|
+
- Handler --> Service
|
|
318
|
+
+ Handler --> Validate
|
|
319
|
+
+ Validate --> Service
|
|
320
|
+
```
|
|
321
|
+
````
|
|
322
|
+
|
|
323
|
+
diffmap draws every line once, so both versions share one layout. Nodes,
|
|
324
|
+
edges, sequence participants, and messages that only Today has are red and
|
|
325
|
+
dashed; those only Proposed has are green. A node whose label differs between
|
|
326
|
+
the versions is amber and shows its Proposed label. Each version must also be
|
|
327
|
+
a valid diagram on its own. To relabel a node, declare it on its own line
|
|
328
|
+
(`- B[Old]`, `+ B[New]`) so its edges are not drawn twice. In a diff,
|
|
329
|
+
`%% ref edge:<index>` counts edges as drawn: within each run of changed lines,
|
|
330
|
+
`+` lines come before `-` lines.
|
|
331
|
+
|
|
332
|
+
````md
|
|
333
|
+
```services diff
|
|
334
|
+
service API
|
|
335
|
+
- service SessionCache
|
|
336
|
+
- state active = 0
|
|
337
|
+
+ service Sessions
|
|
338
|
+
service Postgres
|
|
339
|
+
- state sessions = not used
|
|
340
|
+
+ state sessions = 0 rows
|
|
341
|
+
|
|
342
|
+
start "Sessions move to Postgres"
|
|
343
|
+
-step "API caches the session"
|
|
344
|
+
API -> SessionCache: create(alice)
|
|
345
|
+
SessionCache.active = 1
|
|
346
|
+
+step "Sessions writes the session"
|
|
347
|
+
API -> Sessions: create(alice)
|
|
348
|
+
Sessions -> Postgres: INSERT
|
|
349
|
+
Postgres.sessions = 1 row
|
|
350
|
+
```
|
|
351
|
+
````
|
|
352
|
+
|
|
353
|
+
In a `services diff`, lines inside a `-` or `+` service or step take its
|
|
354
|
+
sign. Today replays the unmarked and `-` lines and Proposed the unmarked and
|
|
355
|
+
`+` lines, so each state row shows Today's value struck through beside
|
|
356
|
+
Proposed's when they differ. A `-` and a `+` declaration of the same state key
|
|
357
|
+
give each version its own starting value. Added and removed steps color their
|
|
358
|
+
segment of the progress bar, and their caption starts with **Added** or
|
|
359
|
+
**Removed**. See [the runnable examples](fixtures/diagram-diffs.md).
|
|
360
|
+
|
|
287
361
|
## Link Mermaid nodes and edges
|
|
288
362
|
|
|
289
363
|
Inside a Mermaid fence, add `%% ref node:<id> [[reference]]` to link a node or
|
package/package.json
CHANGED
package/src/annotations.ts
CHANGED
|
@@ -26,10 +26,26 @@ export type SourceReference = FileReference | ChangesReference;
|
|
|
26
26
|
export type SourceAnnotation = {
|
|
27
27
|
text: string;
|
|
28
28
|
references: SourceReference[];
|
|
29
|
+
/** Source panel title while selected, such as a call stack row without its tree. */
|
|
30
|
+
title?: string;
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
export function parseCallStack(source: string): SourceAnnotation[] {
|
|
32
|
-
return source.split("\n").map(
|
|
34
|
+
return source.split("\n").map((line) => {
|
|
35
|
+
const annotation = parseAnnotation(line);
|
|
36
|
+
return {
|
|
37
|
+
...annotation,
|
|
38
|
+
title: callStackRow(annotation.text).description.trim(),
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A call stack row's change mark, tree prefix, and the call it describes. */
|
|
44
|
+
export function callStackRow(text: string) {
|
|
45
|
+
const sign = text.startsWith("+") ? "+" : text.startsWith("-") ? "-" : " ";
|
|
46
|
+
const label = text.replace(/^[+ -]/, "");
|
|
47
|
+
const tree = /^[ \t│┃├└─]*/u.exec(label)![0];
|
|
48
|
+
return { sign, tree, description: label.slice(tree.length) };
|
|
33
49
|
}
|
|
34
50
|
|
|
35
51
|
export function parseAnnotation(line: string): SourceAnnotation {
|
package/src/extractDocument.ts
CHANGED
|
@@ -12,9 +12,17 @@ export function extractTitle(source: string) {
|
|
|
12
12
|
export function extractFences(source: string) {
|
|
13
13
|
const fences: Fence[] = [];
|
|
14
14
|
for (const match of source.matchAll(fencePattern)) {
|
|
15
|
-
const lang = match[1] ?? "";
|
|
15
|
+
const lang = (match[1] ?? "").trim();
|
|
16
16
|
const body = match[2] ?? "";
|
|
17
|
-
|
|
17
|
+
// A diagram's info string can add `diff`, and Mermaid a [Title].
|
|
18
|
+
const diagram = /^(mermaid|services)\b\s*(diff)?\s*(?:\[([^\]]*)\])?$/.exec(
|
|
19
|
+
lang,
|
|
20
|
+
);
|
|
21
|
+
fences.push(
|
|
22
|
+
diagram === null
|
|
23
|
+
? parseFence(lang, body)
|
|
24
|
+
: parseFence(diagram[1]!, body, [], diagram[3], diagram[2]),
|
|
25
|
+
);
|
|
18
26
|
}
|
|
19
27
|
return fences;
|
|
20
28
|
}
|
package/src/host/home.md
CHANGED
|
@@ -1,125 +1,122 @@
|
|
|
1
1
|
# diffmap
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Your coding agent writes more code than you can review.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**You should be spending time reviewing design decisions and risky changes, not boilerplate.**
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
npx skills add tanishqkancharla/diffmap --skill generate-diffmap
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
The agent keeps the spec in `specs/<name>.md`, serves it on your machine, and can share it as a link like this page.
|
|
12
|
-
|
|
13
|
-
Everything below is an example spec: diffmap's own features, one per phase. Each phase uses the feature it describes. Click a call stack row, a diagram node, or anything marked ↗ to open its code.
|
|
14
|
-
|
|
15
|
-
## Show a change, not a wall of text
|
|
16
|
-
|
|
17
|
-
A coding agent explains a change in a long message. You read it, then open each file it names to check that the code says the same thing.
|
|
18
|
-
|
|
19
|
-
A diffmap puts the explanation next to the code. Each phase says what changes for you, then shows the change as call stacks and diagrams whose parts open the code they name.
|
|
20
|
-
|
|
21
|
-
### ✅ Phase 1: Call stacks that open the code
|
|
7
|
+
`diffmap` is an agent skill that lets your agent visually explain their changes to you. Agents can create interactive diagrams that link back to the source code.
|
|
22
8
|
|
|
23
|
-
|
|
9
|
+
You can try `diffmap` before you install it. In a repository that has changes, paste this prompt into your agent:
|
|
24
10
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
The agent draws the path as data flowing through calls: each row reads `result = call(inputs)`, `+` rows are new, `−` rows go away, and each row opens its code. This is how diffmap draws the tree below:
|
|
30
|
-
|
|
31
|
-
```callstack
|
|
32
|
-
fence = parseFence(lang, source) [[src/parseFence.ts#parseFence]]
|
|
33
|
-
+└── lines = parseCallStack(source) # one row per line, with its links [[src/annotations.ts#parseCallStack]]
|
|
34
|
-
view = Fence({ fence }) [[src/viewer/Fence.tsx#Fence]]
|
|
35
|
-
-└── return CodeBlock(fence.source) # the tree as plain text
|
|
36
|
-
+├── if fence.kind === "callstack":
|
|
37
|
-
+│ └── return CallStackDiff(fence.lines) # rows with + and − marks [[src/viewer/CallStackDiff.tsx#CallStackDiff]]
|
|
38
|
-
+└── return CodeBlock(fence.source) # other languages stay pseudocode
|
|
11
|
+
```text
|
|
12
|
+
Write a diffmap spec for the changes on this branch. Use the instructions at
|
|
13
|
+
https://raw.githubusercontent.com/tanishqkancharla/diffmap/main/skills/generate-diffmap/SKILL.md
|
|
14
|
+
Then serve the spec with npx @tanishqkancharla/diffmap and send me the link.
|
|
39
15
|
```
|
|
40
16
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
**Today**
|
|
44
|
-
|
|
45
|
-
The agent draws a flow in ASCII boxes. The boxes do not link to anything, and they break when a label gets long.
|
|
17
|
+
If you like the result, install the skill:
|
|
46
18
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Mermaid diagrams render in the page, and each node or arrow can open its code. Here is what happens when you click a reference:
|
|
50
|
-
|
|
51
|
-
```mermaid
|
|
52
|
-
flowchart LR
|
|
53
|
-
Click[Click a reference] --> Find[useSourceReference]
|
|
54
|
-
Find -->|diffmap serve| Local[Read the repo with git]
|
|
55
|
-
Find -->|diffmap.dev| GitHub[fetchPinnedBlob]
|
|
56
|
-
Local --> Panel[SourceDiffPanel]
|
|
57
|
-
GitHub --> Panel
|
|
58
|
-
%% ref node:Find [[src/viewer/sourceNavigation.ts#useSourceReference]]
|
|
59
|
-
%% ref node:GitHub [[src/github/fetchRepo.ts#fetchPinnedBlob]]
|
|
60
|
-
%% ref node:Panel [[src/viewer/SourceDiffPanel.tsx#SourceDiffPanel]]
|
|
61
|
-
%% ref edge:1 [[src/viewer/sourceNavigation.ts#requestSource]]
|
|
19
|
+
```sh
|
|
20
|
+
npx skills add tanishqkancharla/diffmap --skill generate-diffmap
|
|
62
21
|
```
|
|
63
22
|
|
|
64
|
-
|
|
23
|
+
`diffmap` is [open source](https://github.com/tanishqkancharla/diffmap) and MIT licensed. Diffmaps are hosted locally by default, but you can choose to [upload a diffmap as a secret gist](#share-a-spec-with-a-link) and open it on diffmap.dev.
|
|
24
|
+
|
|
25
|
+
## See the main ideas
|
|
65
26
|
|
|
66
|
-
|
|
27
|
+
Your agent selects the best diagram for each idea. Each part of the diagram has a link to its code.
|
|
67
28
|
|
|
68
|
-
|
|
29
|
+
### Service diagrams
|
|
69
30
|
|
|
70
|
-
|
|
31
|
+
A service diagram shows each service and its state. Each step shows one event and the state that the event changes. Thus, you do not have to remember the state.
|
|
71
32
|
|
|
72
|
-
|
|
33
|
+
To see how `diffmap serve` opens a spec, press **Play**. To read about a name with a dotted line, put the pointer on the name:
|
|
73
34
|
|
|
74
35
|
```services
|
|
75
36
|
service Laptop
|
|
76
|
-
>
|
|
37
|
+
> This is your computer. diffmap serve reads files here and does not upload them.
|
|
77
38
|
service Agent "coding agent"
|
|
78
|
-
>
|
|
39
|
+
> The agent writes the spec and runs diffmap for you.
|
|
79
40
|
service CLI "diffmap serve" [[src/serve.ts#startServer]]
|
|
80
|
-
>
|
|
41
|
+
> This small server uses git to read your repository.
|
|
81
42
|
state url = none
|
|
82
|
-
service Repo "your
|
|
83
|
-
>
|
|
43
|
+
service Repo "your repository"
|
|
44
|
+
> The repository contains the spec and the commits that the spec refers to.
|
|
84
45
|
service Browser
|
|
85
46
|
service Page "the spec" [[src/viewer/ViewerApp.tsx#ViewerApp]]
|
|
86
47
|
state page = empty
|
|
87
48
|
service Panel "Diff panel" [[src/viewer/SourceDiffPanel.tsx#SourceDiffPanel]]
|
|
88
|
-
>
|
|
49
|
+
> The Diff panel opens next to the spec. It shows the code for the link that you clicked.
|
|
89
50
|
state shows = none
|
|
90
51
|
|
|
52
|
+
start "diffmap serve opens a spec in your browser"
|
|
53
|
+
> The agent has written the spec, but nothing serves it yet.
|
|
54
|
+
|
|
91
55
|
step "The agent serves the spec"
|
|
56
|
+
> Your agent runs diffmap serve with the path of the spec.
|
|
92
57
|
Agent -> CLI: serve specs/search.md
|
|
93
|
-
step "serve
|
|
58
|
+
step "serve examines each commit in the spec"
|
|
59
|
+
> Before the server starts, it uses git to read each commit and line range that the spec refers to.
|
|
94
60
|
CLI -> Repo: readDocumentChanges [[src/git.ts#readDocumentChanges]]
|
|
95
|
-
>
|
|
61
|
+
> If a SHA or a line range is wrong, the command stops here. Thus, the page has no broken links.
|
|
96
62
|
step "serve prints a URL for you"
|
|
63
|
+
> The server starts on your computer and gives the agent a local URL.
|
|
97
64
|
CLI -> Agent: localhost URL
|
|
98
65
|
CLI.url = localhost:4321
|
|
99
66
|
step "You open the page"
|
|
67
|
+
> Your browser gets the spec from the server and shows it as a page.
|
|
100
68
|
Page -> CLI: GET /
|
|
101
69
|
CLI -> Page: spec
|
|
102
70
|
Page.page = rendered
|
|
103
71
|
step "You click a call stack row"
|
|
72
|
+
> The Diff panel asks the server for the code that the row refers to.
|
|
104
73
|
Panel -> CLI: /__diffmap/source
|
|
105
74
|
CLI -> Repo: git show
|
|
106
|
-
>
|
|
75
|
+
> The server reads the file from the commit in the row, not from the working tree.
|
|
107
76
|
step "The Diff panel shows the code"
|
|
77
|
+
> The server sends the lines, and the Diff panel shows them next to the spec.
|
|
108
78
|
CLI -> Panel: lines
|
|
109
79
|
Panel.shows = code
|
|
110
80
|
```
|
|
111
81
|
|
|
112
|
-
###
|
|
82
|
+
### Mermaid diagrams
|
|
83
|
+
|
|
84
|
+
The page shows Mermaid diagrams. Each node and each arrow can have a link to its code. This diagram shows what occurs when you click a link:
|
|
85
|
+
|
|
86
|
+
```mermaid
|
|
87
|
+
flowchart LR
|
|
88
|
+
Click[Click a link] --> Find[useSourceReference]
|
|
89
|
+
Find -->|diffmap serve| Local[Read the repo with git]
|
|
90
|
+
Find -->|diffmap.dev| GitHub[fetchPinnedBlob]
|
|
91
|
+
Local --> Panel[SourceDiffPanel]
|
|
92
|
+
GitHub --> Panel
|
|
93
|
+
%% ref node:Find [[src/viewer/sourceNavigation.ts#useSourceReference]]
|
|
94
|
+
%% ref node:GitHub [[src/github/fetchRepo.ts#fetchPinnedBlob]]
|
|
95
|
+
%% ref node:Panel [[src/viewer/SourceDiffPanel.tsx#SourceDiffPanel]]
|
|
96
|
+
%% ref edge:1 [[src/viewer/sourceNavigation.ts#requestSource]]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Call stacks
|
|
113
100
|
|
|
114
|
-
|
|
101
|
+
Each row has the form `result = call(inputs)`. Thus, you can follow the data from one call to the next call. A `+` row is new code, and a `−` row is old code that the change removes. Each row has a link to its code.
|
|
115
102
|
|
|
116
|
-
|
|
103
|
+
This call stack shows how `diffmap` draws call stacks:
|
|
117
104
|
|
|
118
|
-
|
|
105
|
+
```callstack
|
|
106
|
+
fence = parseFence(lang, source) [[src/parseFence.ts#parseFence]]
|
|
107
|
+
+└── lines = parseCallStack(source) # one row per line, with its links [[src/annotations.ts#parseCallStack]]
|
|
108
|
+
view = Fence({ fence }) [[src/viewer/Fence.tsx#Fence]]
|
|
109
|
+
-└── return CodeBlock(fence.source) # the tree as plain text
|
|
110
|
+
+├── if fence.kind === "callstack":
|
|
111
|
+
+│ └── return CallStackDiff(fence.lines) # rows with + and − marks [[src/viewer/CallStackDiff.tsx#CallStackDiff]]
|
|
112
|
+
+└── return CodeBlock(fence.source) # other languages stay pseudocode
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Open the code
|
|
119
116
|
|
|
120
|
-
A function
|
|
117
|
+
A function name in a sentence can also be a link, for example [[src/share.ts#shareMarkdownFile]].
|
|
121
118
|
|
|
122
|
-
|
|
119
|
+
When the work is complete, the spec shows a card for the commit. The links in the spec then refer to that commit. These links always show the code from that commit, also when the file changes later.
|
|
123
120
|
|
|
124
121
|
```changes
|
|
125
122
|
6e4b5d3
|
|
@@ -136,22 +133,22 @@ This phase is done, so it ends the way a finished phase does: with a card for th
|
|
|
136
133
|
+ └── return InlineReference(node.annotation) # a code button that opens the Diff panel [[6e4b5d3:src/viewer/ComarkView.tsx#L173-L189]]
|
|
137
134
|
```
|
|
138
135
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
**Today**
|
|
142
|
-
|
|
143
|
-
To read a spec, a teammate needs diffmap and a checkout of your repo.
|
|
136
|
+
## Share a spec with a link
|
|
144
137
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
One command uploads the spec as a secret GitHub gist and prints a link anyone can open:
|
|
138
|
+
A teammate can read your spec without a copy of your branch. One command uploads the spec as a secret GitHub gist and prints the link:
|
|
148
139
|
|
|
149
140
|
```sh
|
|
150
141
|
npx @tanishqkancharla/diffmap share specs/search.md
|
|
151
142
|
# prints https://diffmap.dev/g/<gistId>
|
|
152
143
|
```
|
|
153
144
|
|
|
154
|
-
|
|
145
|
+
::details{summary="How sharing works"}
|
|
146
|
+
The shared page cannot use git or a copy of your repository. Thus, the gist also contains the data that the spec uses:
|
|
147
|
+
|
|
148
|
+
- A snapshot of each commit that the spec refers to
|
|
149
|
+
- The current contents of each file that the spec quotes.
|
|
150
|
+
|
|
151
|
+
Because of this data, this page can show code:
|
|
155
152
|
|
|
156
153
|
```mermaid
|
|
157
154
|
flowchart LR
|
|
@@ -172,7 +169,7 @@ flowchart LR
|
|
|
172
169
|
2f96051
|
|
173
170
|
```
|
|
174
171
|
|
|
175
|
-
`share`
|
|
172
|
+
`share` makes the two snapshots before it calls `gh`. Thus, `diffmap` does not keep a GitHub token:
|
|
176
173
|
|
|
177
174
|
```callstack
|
|
178
175
|
result = shareMarkdownFile({ filePath, workspaceRoot }) [[2f96051:src/share.ts#L26-L103]]
|
|
@@ -186,7 +183,7 @@ flowchart LR
|
|
|
186
183
|
└── return { gistId, viewerUrl: gistViewerUrl(gistId) }
|
|
187
184
|
```
|
|
188
185
|
|
|
189
|
-
This excerpt is one of the quoted files,
|
|
186
|
+
This excerpt is from one of the quoted files. Thus, a shared copy of this page also shows it:
|
|
190
187
|
|
|
191
188
|
```54:63:src/share.ts
|
|
192
189
|
// It has no files either, so excerpts and references without a commit read these.
|
|
@@ -200,3 +197,5 @@ This excerpt is one of the quoted files, so a shared copy of this page shows it
|
|
|
200
197
|
if (input.isPublic) args.push("--public");
|
|
201
198
|
args.push("--desc", extractTitle(source), filePath);
|
|
202
199
|
```
|
|
200
|
+
|
|
201
|
+
::
|
package/src/host/home.test.ts
CHANGED
|
@@ -62,7 +62,6 @@ test("homepage markdown is a real viewer document", async () => {
|
|
|
62
62
|
const levels = new Set(document.headings.map((heading) => heading.level));
|
|
63
63
|
assert.equal(levels.has(1), true);
|
|
64
64
|
assert.equal(levels.has(2), true);
|
|
65
|
-
assert.equal(levels.has(3), true);
|
|
66
65
|
assert.ok(
|
|
67
66
|
document.references.filter((ref) => ref.rev !== undefined).length >= 3,
|
|
68
67
|
);
|
package/src/parseFence.ts
CHANGED
|
@@ -8,8 +8,21 @@ import {
|
|
|
8
8
|
import { parseServiceDiagram, type ServicesParse } from "./services.js";
|
|
9
9
|
|
|
10
10
|
export type Fence =
|
|
11
|
-
| {
|
|
12
|
-
|
|
11
|
+
| {
|
|
12
|
+
kind: "mermaid";
|
|
13
|
+
/** In a diff, every line with its sign removed; see mermaidFence. */
|
|
14
|
+
source: string;
|
|
15
|
+
annotations: DiagramAnnotation[];
|
|
16
|
+
/** From ```mermaid diff: the diagram without `+` lines, and without `-` lines. */
|
|
17
|
+
sides: { today: string; proposed: string } | undefined;
|
|
18
|
+
/** From the info string, as in ```mermaid [Request flow]. */
|
|
19
|
+
title: string | undefined;
|
|
20
|
+
}
|
|
21
|
+
| {
|
|
22
|
+
kind: "services";
|
|
23
|
+
source: string;
|
|
24
|
+
parsed: ServicesParse;
|
|
25
|
+
}
|
|
13
26
|
| { kind: "html"; source: string }
|
|
14
27
|
| {
|
|
15
28
|
kind: "callstack";
|
|
@@ -19,6 +32,8 @@ export type Fence =
|
|
|
19
32
|
/** 1-based rows from the info string, as in ```callstack {2}. */
|
|
20
33
|
highlights: number[];
|
|
21
34
|
notes: CodeNote[];
|
|
35
|
+
/** From the info string, as in ```callstack [Request flow]. */
|
|
36
|
+
title: string | undefined;
|
|
22
37
|
}
|
|
23
38
|
| {
|
|
24
39
|
kind: "changes";
|
|
@@ -37,6 +52,8 @@ export type Fence =
|
|
|
37
52
|
/** 1-based lines from the info string, as in ```ts {2,4-6}. */
|
|
38
53
|
highlights: number[];
|
|
39
54
|
notes: CodeNote[];
|
|
55
|
+
/** From the info string, as in ```ts [src/request.ts]. */
|
|
56
|
+
title: string | undefined;
|
|
40
57
|
};
|
|
41
58
|
|
|
42
59
|
const fileRefPattern = /^(\d+):(\d+):(.+)$/;
|
|
@@ -60,22 +77,21 @@ export function parseFence(
|
|
|
60
77
|
lang: string,
|
|
61
78
|
source: string,
|
|
62
79
|
highlights: number[] = [],
|
|
80
|
+
title?: string,
|
|
81
|
+
/** The info string after the language and highlights, as in ```mermaid diff. */
|
|
82
|
+
meta?: string,
|
|
63
83
|
): Fence {
|
|
64
84
|
const trimmed = source.replace(/\n$/, "");
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
kind: "mermaid",
|
|
68
|
-
source: trimmed,
|
|
69
|
-
annotations: parseDiagramAnnotations(trimmed),
|
|
70
|
-
};
|
|
85
|
+
const diff = meta?.split(/\s+/).includes("diff") ?? false;
|
|
86
|
+
if (lang === "mermaid") return mermaidFence(trimmed, diff, title);
|
|
71
87
|
if (lang === "services")
|
|
72
88
|
return {
|
|
73
89
|
kind: "services",
|
|
74
90
|
source: trimmed,
|
|
75
|
-
parsed: parseServiceDiagram(trimmed),
|
|
91
|
+
parsed: parseServiceDiagram(trimmed, diff),
|
|
76
92
|
};
|
|
77
93
|
if (lang === "html") return { kind: "html", source: trimmed };
|
|
78
|
-
if (lang === "callstack") return callStackFence(trimmed, highlights);
|
|
94
|
+
if (lang === "callstack") return callStackFence(trimmed, highlights, title);
|
|
79
95
|
|
|
80
96
|
// The first line is the rev (blank for uncommitted); each later line is a path to review.
|
|
81
97
|
if (lang === "changes") {
|
|
@@ -101,7 +117,8 @@ export function parseFence(
|
|
|
101
117
|
|
|
102
118
|
const diffPath = diffPathPattern.exec(lang);
|
|
103
119
|
if (lang === "diff" || diffPath !== null) {
|
|
104
|
-
if (isCallStackSource(trimmed))
|
|
120
|
+
if (isCallStackSource(trimmed))
|
|
121
|
+
return callStackFence(trimmed, highlights, title);
|
|
105
122
|
return {
|
|
106
123
|
kind: "diff",
|
|
107
124
|
path: diffPath === null ? pathFromDiffSource(trimmed) : diffPath[1],
|
|
@@ -116,10 +133,61 @@ export function parseFence(
|
|
|
116
133
|
source: code,
|
|
117
134
|
highlights,
|
|
118
135
|
notes,
|
|
136
|
+
title,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* A diff's lines start with `-` (Today only), `+` (Proposed only), or
|
|
142
|
+
* anything else (both). The viewer draws every line, then marks what each
|
|
143
|
+
* side lacks, so Mermaid syntax never needs parsing here.
|
|
144
|
+
*/
|
|
145
|
+
function mermaidFence(
|
|
146
|
+
source: string,
|
|
147
|
+
diff: boolean,
|
|
148
|
+
title: string | undefined,
|
|
149
|
+
): Fence {
|
|
150
|
+
if (!diff)
|
|
151
|
+
return {
|
|
152
|
+
kind: "mermaid",
|
|
153
|
+
source,
|
|
154
|
+
annotations: parseDiagramAnnotations(source),
|
|
155
|
+
sides: undefined,
|
|
156
|
+
title,
|
|
157
|
+
};
|
|
158
|
+
const rows = source.split("\n").map((line) => ({
|
|
159
|
+
sign: line[0],
|
|
160
|
+
text: line.replace(/^[+ -]/, ""),
|
|
161
|
+
}));
|
|
162
|
+
const without = (sign: string) =>
|
|
163
|
+
rows
|
|
164
|
+
.filter((row) => row.sign !== sign)
|
|
165
|
+
.map((row) => row.text)
|
|
166
|
+
.join("\n");
|
|
167
|
+
// Mermaid keeps a node's first label, so within each run of changed lines
|
|
168
|
+
// the `+` lines come first and a relabeled node shows its Proposed label.
|
|
169
|
+
const merged: string[] = [];
|
|
170
|
+
const removed: string[] = [];
|
|
171
|
+
for (const row of rows) {
|
|
172
|
+
if (row.sign === "-") removed.push(row.text);
|
|
173
|
+
else if (row.sign === "+") merged.push(row.text);
|
|
174
|
+
else merged.push(...removed.splice(0), row.text);
|
|
175
|
+
}
|
|
176
|
+
merged.push(...removed);
|
|
177
|
+
return {
|
|
178
|
+
kind: "mermaid",
|
|
179
|
+
source: merged.join("\n"),
|
|
180
|
+
annotations: parseDiagramAnnotations(merged.join("\n")),
|
|
181
|
+
sides: { today: without("+"), proposed: without("-") },
|
|
182
|
+
title,
|
|
119
183
|
};
|
|
120
184
|
}
|
|
121
185
|
|
|
122
|
-
function callStackFence(
|
|
186
|
+
function callStackFence(
|
|
187
|
+
source: string,
|
|
188
|
+
highlights: number[],
|
|
189
|
+
title: string | undefined,
|
|
190
|
+
): Fence {
|
|
123
191
|
const { code, notes } = parseCodeNotes(source);
|
|
124
192
|
return {
|
|
125
193
|
kind: "callstack",
|
|
@@ -127,5 +195,6 @@ function callStackFence(source: string, highlights: number[]): Fence {
|
|
|
127
195
|
lines: parseCallStack(code),
|
|
128
196
|
highlights,
|
|
129
197
|
notes,
|
|
198
|
+
title,
|
|
130
199
|
};
|
|
131
200
|
}
|