@tanishqkancharla/diffmap 0.4.0 → 0.6.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 +78 -4
- package/package.json +1 -1
- package/src/annotations.ts +17 -1
- package/src/extractDocument.ts +10 -2
- package/src/host/home.md +89 -103
- package/src/host/home.test.ts +11 -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 +122 -15
- package/src/viewer/CallStackDiff.tsx +29 -33
- package/src/viewer/ChangesCard.tsx +0 -2
- package/src/viewer/CodeBlock.tsx +79 -7
- 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 +90 -54
- package/src/viewer/ServiceStage.tsx +206 -60
- 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/notes.tsx +0 -1
- 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
|
+
> The API checks her password with Auth, then sets a session cookie.
|
|
247
262
|
step "The browser submits the form"
|
|
248
263
|
Browser -> API: POST /login
|
|
249
264
|
step "The API asks Auth to verify"
|
|
@@ -261,16 +276,21 @@ 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
|
+
gives an overview of the whole process. A `services` fence takes no
|
|
283
|
+
`[Title]`; 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.
|
|
267
|
-
|
|
268
|
-
|
|
287
|
+
A step's events travel one after another, in the order written, and the
|
|
288
|
+
updates land when the last one arrives.
|
|
269
289
|
- A `>` line adds text to the line above it. Indent it further than that line;
|
|
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,109 @@
|
|
|
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
|
+
|
|
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.
|
|
8
|
+
|
|
9
|
+
You can try `diffmap` before you install it. In a repository that has changes, paste this prompt into your agent:
|
|
10
|
+
|
|
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.
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
If you like the result, install the skill:
|
|
6
18
|
|
|
7
19
|
```sh
|
|
8
20
|
npx skills add tanishqkancharla/diffmap --skill generate-diffmap
|
|
9
21
|
```
|
|
10
22
|
|
|
11
|
-
|
|
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.
|
|
12
24
|
|
|
13
|
-
|
|
25
|
+
## See the main ideas
|
|
14
26
|
|
|
15
|
-
|
|
27
|
+
Your agent selects the best diagram for each idea. Each part of the diagram has a link to its code.
|
|
16
28
|
|
|
17
|
-
|
|
29
|
+
### Service diagrams
|
|
18
30
|
|
|
19
|
-
A
|
|
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.
|
|
20
32
|
|
|
21
|
-
|
|
33
|
+
A service diagram can also show a change. Red parts exist only today, and green parts are new. As you step, each value shows today's state crossed out next to the new state.
|
|
22
34
|
|
|
23
|
-
**
|
|
35
|
+
This change moves login sessions from memory to a database. Press **Play** to step through it:
|
|
24
36
|
|
|
25
|
-
|
|
37
|
+
```services diff
|
|
38
|
+
service Browser "login page"
|
|
39
|
+
state cookie = none
|
|
40
|
+
service API "HTTP server"
|
|
41
|
+
- service Cache "in-memory sessions"
|
|
42
|
+
- state sessions = 0
|
|
43
|
+
+ service Sessions
|
|
44
|
+
service Postgres
|
|
45
|
+
- state sessions = not used
|
|
46
|
+
+ state sessions = 0 rows
|
|
26
47
|
|
|
27
|
-
|
|
48
|
+
start "Sessions move from memory to Postgres"
|
|
49
|
+
> Alice logs in, and the API stores her session. Today it keeps the session in memory, so a server restart signs her out. After the change, Sessions writes it to Postgres, where it survives a restart.
|
|
28
50
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
51
|
+
step "Alice logs in"
|
|
52
|
+
Browser -> API: POST /login
|
|
53
|
+
-step "The API keeps the session in memory"
|
|
54
|
+
API -> Cache: create(alice)
|
|
55
|
+
Cache.sessions = 1
|
|
56
|
+
> The server loses this when it restarts.
|
|
57
|
+
+step "Sessions writes the session to Postgres"
|
|
58
|
+
API -> Sessions: create(alice)
|
|
59
|
+
Sessions -> Postgres: INSERT s_81f
|
|
60
|
+
Postgres.sessions = 1 row
|
|
61
|
+
> The row survives a restart.
|
|
62
|
+
step "The API sets the session cookie"
|
|
63
|
+
API -> Browser: 200 Set-Cookie
|
|
64
|
+
Browser.cookie = sid=s_81f
|
|
39
65
|
```
|
|
40
66
|
|
|
41
|
-
###
|
|
42
|
-
|
|
43
|
-
**Today**
|
|
67
|
+
### Mermaid diagrams
|
|
44
68
|
|
|
45
|
-
The
|
|
69
|
+
The page shows Mermaid diagrams. Each node and each arrow can have a link to its code. A diagram can show a change in one picture: green parts are new, and red parts are removed.
|
|
46
70
|
|
|
47
|
-
|
|
71
|
+
This diagram shows what occurs when you click a link, and what changed when diffmap.dev began to read code from GitHub:
|
|
48
72
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
%% ref node:Panel [[src/viewer/SourceDiffPanel.tsx#SourceDiffPanel]]
|
|
61
|
-
%% ref edge:1 [[src/viewer/sourceNavigation.ts#requestSource]]
|
|
73
|
+
```mermaid diff [diffmap.dev reads linked code from GitHub]
|
|
74
|
+
flowchart LR
|
|
75
|
+
Click[Click a link] --> Find[useSourceReference]
|
|
76
|
+
Find -->|diffmap serve| Local[Read the repo with git]
|
|
77
|
+
+ Find -->|diffmap.dev| GitHub[fetchPinnedBlob]
|
|
78
|
+
Local --> Panel[SourceDiffPanel]
|
|
79
|
+
+ GitHub --> Panel
|
|
80
|
+
%% ref node:Find [[src/viewer/sourceNavigation.ts#useSourceReference]]
|
|
81
|
+
%% ref node:GitHub [[src/github/fetchRepo.ts#fetchPinnedBlob]]
|
|
82
|
+
%% ref node:Panel [[src/viewer/SourceDiffPanel.tsx#SourceDiffPanel]]
|
|
83
|
+
%% ref edge:1 [[src/viewer/sourceNavigation.ts#requestSource]]
|
|
62
84
|
```
|
|
63
85
|
|
|
64
|
-
###
|
|
65
|
-
|
|
66
|
-
**Today**
|
|
67
|
-
|
|
68
|
-
A sequence diagram shows the messages between services, but not what each message changes. You keep the state of every service in your head.
|
|
69
|
-
|
|
70
|
-
**Proposed**
|
|
71
|
-
|
|
72
|
-
A service diagram shows each service with its state, then steps through the events. Press **Play** to watch `diffmap serve` open a spec, or hover a dotted name to read what it is:
|
|
73
|
-
|
|
74
|
-
```services
|
|
75
|
-
service Laptop
|
|
76
|
-
> Your machine. serve reads files here and uploads nothing.
|
|
77
|
-
service Agent "coding agent"
|
|
78
|
-
> Writes the spec and runs diffmap for you.
|
|
79
|
-
service CLI "diffmap serve" [[src/serve.ts#startServer]]
|
|
80
|
-
> A small server that reads your repo with git.
|
|
81
|
-
state url = none
|
|
82
|
-
service Repo "your repo"
|
|
83
|
-
> Holds the spec and the commits it links to.
|
|
84
|
-
service Browser
|
|
85
|
-
service Page "the spec" [[src/viewer/ViewerApp.tsx#ViewerApp]]
|
|
86
|
-
state page = empty
|
|
87
|
-
service Panel "Diff panel" [[src/viewer/SourceDiffPanel.tsx#SourceDiffPanel]]
|
|
88
|
-
> Opens beside the spec and shows the code a reference names.
|
|
89
|
-
state shows = none
|
|
90
|
-
|
|
91
|
-
step "The agent serves the spec"
|
|
92
|
-
Agent -> CLI: serve specs/search.md
|
|
93
|
-
step "serve checks every commit the spec links to"
|
|
94
|
-
CLI -> Repo: readDocumentChanges [[src/git.ts#readDocumentChanges]]
|
|
95
|
-
> A wrong SHA or line range fails here, so you never meet a broken link.
|
|
96
|
-
step "serve prints a URL for you"
|
|
97
|
-
CLI -> Agent: localhost URL
|
|
98
|
-
CLI.url = localhost:4321
|
|
99
|
-
step "You open the page"
|
|
100
|
-
Page -> CLI: GET /
|
|
101
|
-
CLI -> Page: spec
|
|
102
|
-
Page.page = rendered
|
|
103
|
-
step "You click a call stack row"
|
|
104
|
-
Panel -> CLI: /__diffmap/source
|
|
105
|
-
CLI -> Repo: git show
|
|
106
|
-
> Reads the file at the commit the row names, not the working tree.
|
|
107
|
-
step "The Diff panel shows the code"
|
|
108
|
-
CLI -> Panel: lines
|
|
109
|
-
Panel.shows = code
|
|
110
|
-
```
|
|
86
|
+
### Call stacks
|
|
111
87
|
|
|
112
|
-
|
|
88
|
+
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.
|
|
113
89
|
|
|
114
|
-
|
|
90
|
+
This call stack shows how `diffmap` draws call stacks:
|
|
115
91
|
|
|
116
|
-
|
|
92
|
+
```callstack
|
|
93
|
+
fence = parseFence(lang, source) [[src/parseFence.ts#parseFence]]
|
|
94
|
+
+└── lines = parseCallStack(source) # one row per line, with its links [[src/annotations.ts#parseCallStack]]
|
|
95
|
+
view = Fence({ fence }) [[src/viewer/Fence.tsx#Fence]]
|
|
96
|
+
-└── return CodeBlock(fence.source) # the tree as plain text
|
|
97
|
+
+├── if fence.kind === "callstack":
|
|
98
|
+
+│ └── return CallStackDiff(fence.lines) # rows with + and − marks [[src/viewer/CallStackDiff.tsx#CallStackDiff]]
|
|
99
|
+
+└── return CodeBlock(fence.source) # other languages stay pseudocode
|
|
100
|
+
```
|
|
117
101
|
|
|
118
|
-
|
|
102
|
+
## Open the code
|
|
119
103
|
|
|
120
|
-
A function
|
|
104
|
+
A function name in a sentence can also be a link, for example [[src/share.ts#shareMarkdownFile]].
|
|
121
105
|
|
|
122
|
-
|
|
106
|
+
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
107
|
|
|
124
108
|
```changes
|
|
125
109
|
6e4b5d3
|
|
@@ -136,22 +120,22 @@ This phase is done, so it ends the way a finished phase does: with a card for th
|
|
|
136
120
|
+ └── return InlineReference(node.annotation) # a code button that opens the Diff panel [[6e4b5d3:src/viewer/ComarkView.tsx#L173-L189]]
|
|
137
121
|
```
|
|
138
122
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
**Today**
|
|
142
|
-
|
|
143
|
-
To read a spec, a teammate needs diffmap and a checkout of your repo.
|
|
123
|
+
## Share a spec with a link
|
|
144
124
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
One command uploads the spec as a secret GitHub gist and prints a link anyone can open:
|
|
125
|
+
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
126
|
|
|
149
127
|
```sh
|
|
150
128
|
npx @tanishqkancharla/diffmap share specs/search.md
|
|
151
129
|
# prints https://diffmap.dev/g/<gistId>
|
|
152
130
|
```
|
|
153
131
|
|
|
154
|
-
|
|
132
|
+
::details{summary="How sharing works"}
|
|
133
|
+
The shared page cannot use git or a copy of your repository. Thus, the gist also contains the data that the spec uses:
|
|
134
|
+
|
|
135
|
+
- A snapshot of each commit that the spec refers to
|
|
136
|
+
- The current contents of each file that the spec quotes.
|
|
137
|
+
|
|
138
|
+
Because of this data, this page can show code:
|
|
155
139
|
|
|
156
140
|
```mermaid
|
|
157
141
|
flowchart LR
|
|
@@ -172,7 +156,7 @@ flowchart LR
|
|
|
172
156
|
2f96051
|
|
173
157
|
```
|
|
174
158
|
|
|
175
|
-
`share`
|
|
159
|
+
`share` makes the two snapshots before it calls `gh`. Thus, `diffmap` does not keep a GitHub token:
|
|
176
160
|
|
|
177
161
|
```callstack
|
|
178
162
|
result = shareMarkdownFile({ filePath, workspaceRoot }) [[2f96051:src/share.ts#L26-L103]]
|
|
@@ -186,7 +170,7 @@ flowchart LR
|
|
|
186
170
|
└── return { gistId, viewerUrl: gistViewerUrl(gistId) }
|
|
187
171
|
```
|
|
188
172
|
|
|
189
|
-
This excerpt is one of the quoted files,
|
|
173
|
+
This excerpt is from one of the quoted files. Thus, a shared copy of this page also shows it:
|
|
190
174
|
|
|
191
175
|
```54:63:src/share.ts
|
|
192
176
|
// It has no files either, so excerpts and references without a commit read these.
|
|
@@ -200,3 +184,5 @@ This excerpt is one of the quoted files, so a shared copy of this page shows it
|
|
|
200
184
|
if (input.isPublic) args.push("--public");
|
|
201
185
|
args.push("--desc", extractTitle(source), filePath);
|
|
202
186
|
```
|
|
187
|
+
|
|
188
|
+
::
|
package/src/host/home.test.ts
CHANGED
|
@@ -37,6 +37,17 @@ test("homepage markdown is a real viewer document", async () => {
|
|
|
37
37
|
const mermaid = fences.find((fence) => fence.kind === "mermaid");
|
|
38
38
|
assert.notEqual(mermaid, undefined);
|
|
39
39
|
if (mermaid?.kind !== "mermaid") return;
|
|
40
|
+
// The homepage shows diagram diffs, not only call stack diffs.
|
|
41
|
+
assert.notEqual(mermaid.sides, undefined);
|
|
42
|
+
assert.equal(
|
|
43
|
+
fences.some(
|
|
44
|
+
(fence) =>
|
|
45
|
+
fence.kind === "services" &&
|
|
46
|
+
fence.parsed.ok &&
|
|
47
|
+
fence.parsed.diagram.diff,
|
|
48
|
+
),
|
|
49
|
+
true,
|
|
50
|
+
);
|
|
40
51
|
assert.equal(
|
|
41
52
|
mermaid.annotations.some((link) =>
|
|
42
53
|
link.annotation.references.some((ref) => ref.kind === "file"),
|
|
@@ -62,7 +73,6 @@ test("homepage markdown is a real viewer document", async () => {
|
|
|
62
73
|
const levels = new Set(document.headings.map((heading) => heading.level));
|
|
63
74
|
assert.equal(levels.has(1), true);
|
|
64
75
|
assert.equal(levels.has(2), true);
|
|
65
|
-
assert.equal(levels.has(3), true);
|
|
66
76
|
assert.ok(
|
|
67
77
|
document.references.filter((ref) => ref.rev !== undefined).length >= 3,
|
|
68
78
|
);
|
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
|
}
|