@tanishqkancharla/diffmap 0.6.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 CHANGED
@@ -258,7 +258,7 @@ service API "HTTP server" [[src/api.ts]]
258
258
  > Failures since the last success. Five locks the account.
259
259
 
260
260
  start "Alice logs in"
261
- > She is signed out.
261
+ > The API checks her password with Auth, then sets a session cookie.
262
262
  step "The browser submits the form"
263
263
  Browser -> API: POST /login
264
264
  step "The API asks Auth to verify"
@@ -279,13 +279,13 @@ step "The API sets the session cookie"
279
279
  - `start "caption"` at column 0, before the first step, captions the whole
280
280
  diagram, such as `start "Alice logs in"`. It shows above the diagram until
281
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.
282
+ gives an overview of the whole process. A `services` fence takes no
283
+ `[Title]`; the start caption is its title.
284
284
  - `step "caption"` starts a step at column 0. The caption is required: a
285
285
  sentence that says what happens, shown under the controls. Indented lines
286
286
  under it are `From -> To: label` events and `Service.key = value` updates.
287
- Every event in a step travels at once, and the updates land when the events
288
- arrive.
287
+ A step's events travel one after another, in the order written, and the
288
+ updates land when the last one arrives.
289
289
  - A `>` line adds text to the line above it. Indent it further than that line;
290
290
  consecutive `>` lines join.
291
291
  - Under a service or state, it is a description: the name gets a dotted
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanishqkancharla/diffmap",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Serve a spec or code walkthrough as a local page",
5
5
  "license": "MIT",
6
6
  "author": "Tanishq Kancharla",
package/src/host/home.md CHANGED
@@ -30,70 +30,57 @@ Your agent selects the best diagram for each idea. Each part of the diagram has
30
30
 
31
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.
32
32
 
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:
34
-
35
- ```services
36
- service Laptop
37
- > This is your computer. diffmap serve reads files here and does not upload them.
38
- service Agent "coding agent"
39
- > The agent writes the spec and runs diffmap for you.
40
- service CLI "diffmap serve" [[src/serve.ts#startServer]]
41
- > This small server uses git to read your repository.
42
- state url = none
43
- service Repo "your repository"
44
- > The repository contains the spec and the commits that the spec refers to.
45
- service Browser
46
- service Page "the spec" [[src/viewer/ViewerApp.tsx#ViewerApp]]
47
- state page = empty
48
- service Panel "Diff panel" [[src/viewer/SourceDiffPanel.tsx#SourceDiffPanel]]
49
- > The Diff panel opens next to the spec. It shows the code for the link that you clicked.
50
- state shows = none
51
-
52
- start "diffmap serve opens a spec in your browser"
53
- > The agent has written the spec, but nothing serves it yet.
54
-
55
- step "The agent serves the spec"
56
- > Your agent runs diffmap serve with the path of the spec.
57
- Agent -> CLI: serve specs/search.md
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.
60
- CLI -> Repo: readDocumentChanges [[src/git.ts#readDocumentChanges]]
61
- > If a SHA or a line range is wrong, the command stops here. Thus, the page has no broken links.
62
- step "serve prints a URL for you"
63
- > The server starts on your computer and gives the agent a local URL.
64
- CLI -> Agent: localhost URL
65
- CLI.url = localhost:4321
66
- step "You open the page"
67
- > Your browser gets the spec from the server and shows it as a page.
68
- Page -> CLI: GET /
69
- CLI -> Page: spec
70
- Page.page = rendered
71
- step "You click a call stack row"
72
- > The Diff panel asks the server for the code that the row refers to.
73
- Panel -> CLI: /__diffmap/source
74
- CLI -> Repo: git show
75
- > The server reads the file from the commit in the row, not from the working tree.
76
- step "The Diff panel shows the code"
77
- > The server sends the lines, and the Diff panel shows them next to the spec.
78
- CLI -> Panel: lines
79
- Panel.shows = code
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.
34
+
35
+ This change moves login sessions from memory to a database. Press **Play** to step through it:
36
+
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
47
+
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.
50
+
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
80
65
  ```
81
66
 
82
67
  ### Mermaid diagrams
83
68
 
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]]
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.
70
+
71
+ This diagram shows what occurs when you click a link, and what changed when diffmap.dev began to read code from GitHub:
72
+
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]]
97
84
  ```
98
85
 
99
86
  ### Call stacks
@@ -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"),
package/src/services.ts CHANGED
@@ -2,8 +2,8 @@ import { parseAnnotation, type SourceAnnotation } from "./annotations.js";
2
2
 
3
3
  /**
4
4
  * A `services` fence: services that hold state, nest sub-services, and send
5
- * each other events. Steps replay the events in order. Every event in a step
6
- * is in flight at once; the step's state updates land when they arrive.
5
+ * each other events. Steps replay the events in order. A step's events travel
6
+ * one after another; its state updates land when the last one arrives.
7
7
  */
8
8
  export type ServiceDiagram = {
9
9
  services: Service[];
@@ -128,9 +128,8 @@ const rowSize = defineVars({
128
128
  });
129
129
 
130
130
  const styles = {
131
- // Sized like Maui's code block, so stacks and code read as one family.
131
+ // Sized like Maui's code block, but in the proportional UI font.
132
132
  shell: style(text({ size: "xs", fontWeight: 400, color: "highContrast" }), {
133
- fontFamily: codeFontFamily,
134
133
  margin: 0,
135
134
  paddingBlock: "8px",
136
135
  overflowX: "hidden",
@@ -172,7 +171,6 @@ const styles = {
172
171
  backgroundColor: colors.red[5],
173
172
  boxShadow: `inset 3px 0 ${colors.red[9]}`,
174
173
  },
175
- "&[data-note]": { cursor: "help" },
176
174
  // The sign fills the row gutter, so the dot follows the label instead.
177
175
  "&[data-note='start'] .stack-label::after": {
178
176
  content: '""',
@@ -190,7 +190,6 @@ const styles = {
190
190
  "& .line-note": {
191
191
  display: "block",
192
192
  position: "relative",
193
- cursor: "help",
194
193
  outline: "none",
195
194
  },
196
195
  "& .line-note::before": lineNoteDot,
@@ -92,14 +92,15 @@ function ServicePlayer(props: { diagram: ServiceDiagram } & SourceNavigation) {
92
92
  const goTo = (target: number, animate: boolean) =>
93
93
  setPosition(positionFor(diagram, target, animate && !reduceMotion));
94
94
 
95
+ // Events travel one after another, so the step settles when the last lands.
95
96
  useEffect(() => {
96
97
  if (position.phase !== "travel") return;
97
98
  const timer = setTimeout(
98
99
  () => setPosition({ step: position.step, phase: "settled" }),
99
- TRAVEL_MS,
100
+ TRAVEL_MS * (diagram.steps[position.step - 1]?.events.length ?? 1),
100
101
  );
101
102
  return () => clearTimeout(timer);
102
- }, [position]);
103
+ }, [position, diagram]);
103
104
 
104
105
  useEffect(() => {
105
106
  if (!playing || position.phase !== "settled") return;
@@ -536,22 +536,27 @@ function EventMarks(
536
536
  const label = props.event.label === "" ? undefined : pillText(props.event);
537
537
  const lengths = cumulativeLengths(props.points);
538
538
  const total = lengths.at(-1)! || 1;
539
+ // A step's events leave in order, each when the one before it arrives.
540
+ const delay = props.phase === "travel" ? (props.index * TRAVEL_MS) / 1000 : 0;
539
541
  return (
540
542
  <>
541
543
  {props.phase === "travel" && (
542
544
  <motion.div
543
545
  className={packet}
544
546
  data-change={props.event.sign}
545
- initial={{ x: props.points[0]!.x, y: props.points[0]!.y }}
547
+ initial={{ x: props.points[0]!.x, y: props.points[0]!.y, opacity: 0 }}
546
548
  animate={{
547
549
  x: props.points.map((point) => point.x),
548
550
  y: props.points.map((point) => point.y),
551
+ opacity: 1,
549
552
  }}
550
553
  // Even speed along every segment of the route.
551
554
  transition={{
555
+ delay,
552
556
  duration: TRAVEL_MS / 1000,
553
557
  ease: "easeInOut",
554
558
  times: lengths.map((length) => length / total),
559
+ opacity: { delay, duration: 0 },
555
560
  }}
556
561
  />
557
562
  )}
@@ -563,6 +568,7 @@ function EventMarks(
563
568
  data-event-label={props.index}
564
569
  initial={{ opacity: 0 }}
565
570
  animate={{ opacity: 1 }}
571
+ transition={{ delay }}
566
572
  >
567
573
  {label !== undefined &&
568
574
  (annotation === undefined ? (
@@ -911,7 +917,6 @@ const described = {
911
917
  textDecorationStyle: "dotted" as const,
912
918
  textDecorationColor: colors.gray[8],
913
919
  textUnderlineOffset: "3px",
914
- cursor: "help",
915
920
  },
916
921
  };
917
922
 
@@ -1002,7 +1007,7 @@ const styles = {
1002
1007
  focusRing(),
1003
1008
  {
1004
1009
  minWidth: 0,
1005
- "& > [data-described]": described["&[data-described='true']"],
1010
+ "& > [data-described='true']": described["&[data-described='true']"],
1006
1011
  },
1007
1012
  ),
1008
1013
  headerButton: style({
@@ -105,7 +105,6 @@ export function NoteText(props: { text: string }) {
105
105
 
106
106
  const symbolNoteClass = style(focusRing(), {
107
107
  borderRadius: "3px",
108
- cursor: "help",
109
108
  textDecorationLine: "underline",
110
109
  textDecorationStyle: "dotted",
111
110
  textDecorationColor: colors.gray[9],