Change map
The change map plugin turns the review of a code change into a review of its architecture. A pull request, or a branch an agent finished, is shown as a diagram of the parts it touches and the interactions between them, rather than as a list of files. You see what the change does to the system first, and then open the code of any part, its lines highlighted in the diff.
When a diagram of parts is not enough, the agent adds sequence diagrams for the order of the calls and state diagrams for a lifecycle, so you review how the change behaves as well as what it touches. Parts of the interface carry screenshots before and after, or recordings of the part in use, so you see what the person using the software will see. Each comment goes back on a part, an interaction, lines of a file, an area of a screenshot, a moment of a recording or an entity of a diagram, so the agent knows exactly what it is about.
When to use it
Section titled “When to use it”- A pull or merge request that touches several parts of a system, read as a whole before you read it file by file.
- A change an agent made on its own, reviewed before it opens a pull request or merges.
- A change to an interface, with screenshots of each screen before and after, or a recording of how it behaves.
- A change to a flow or a lifecycle, such as a retry, a handshake or a new status, explained with sequence and state diagrams for a detailed architectural review.
To review the comments an agent wants to post on a pull request, use Code review instead.
Install
Section titled “Install”The plugin comes with the app. Install it in Settings › Plugins, or from the command line:
pinrail plugins install change-mapWhat you see
Section titled “What you see”The map
Section titled “The map”Each part of the change, such as a component, a module, an endpoint or a table, is a card, and the cards are grouped into lanes such as Web app, API and Database. Each interaction between two parts is a line, labelled with what it does.
- A card shows the part’s kind and status in a header tinted with the status colour: new, changed, removed or unchanged. Under its title, a bar and two counts show the lines its part of the change adds and removes. A part of the interface also shows its first screenshot.
- An interaction’s label shows how many files implement it, so an interaction that no code implements has no count.
- The selected part has a thicker border in the accent colour.
The buttons in the map’s top-left corner zoom in and out, fit the map to the view, and switch to compact cards, which show the title and the status only. The choice of cards is kept as a setting.
The side panel
Section titled “The side panel”With nothing selected, the side panel shows the change as a whole:
- The change’s summary, its branches and a link to it, and how many of the change’s changed lines are on the map, not counting changelogs and lock files.
- The parts changed, each with a check on its icon once you have reviewed it and the number of its comments. Choosing one zooms the map in on it.
- The diagrams, and the other files, which no part or interaction names.
- The files of the change, in a drawer at the bottom of the panel. Each file shows its line counts and how many of its lines are off the map, and a filter narrows the list. Choosing a file opens it in the diff drawer and highlights the parts, interactions and diagram entities that touch it, while the rest of the map and the diagrams dims.
Selecting a part or an interaction shows its summary, its files, its screenshots, the diagrams it appears in and its comments instead. You can drag the panel’s left edge to make it wider.
The diff drawer
Section titled “The diff drawer”The diff drawer under the map shows the selected part’s files, with the lines that implement it highlighted. Selecting another part of the same file scrolls to its lines. A changed line that no part, interaction or diagram covers has an amber edge, and the drawer’s header counts those lines. The button in the header shows or hides the drawer, and the choice is kept as a setting.
Sequence and state diagrams
Section titled “Sequence and state diagrams”A change about the order of things or about a lifecycle is hard to read from a map of parts alone. For such a change, the agent adds diagrams of their own, each in a tab above the map. They are optional, and an agent adds one only when the change needs it.
- A sequence diagram shows the participants from left to right, with a lifeline under each, and the messages between them from top to bottom: calls, asynchronous messages, replies and messages to the sender itself. Frames mark the alternatives, the optional runs, the loops and the parallel runs.
- A state diagram shows the states, with a dot for the start and a ringed dot for the end, and the transitions between them, labelled with their event, guard and action. It runs from left to right, and a button beside its zoom buttons lays it out from top to bottom instead. That choice is kept as a setting.
Every participant, message, state and transition has a status, a summary and the files and lines that implement it, which open in the drawer, as a part of the map does. A participant or a message that stands for a part or an interaction of the map is the same thing as that part or interaction, so its files, comments and reviewed mark are shared and nothing is reviewed twice. The tabs show how many comments each diagram has.
Screenshots of interface changes
Section titled “Screenshots of interface changes”A part of the interface, such as a form or a page, can carry screenshots, each with a before and an after. The side panel shows each screen in turn, and a screenshot opens larger in the drawer:
- Side by side, the before and the after at the same zoom and position, so a detail lines up across both.
- With a slider over the two, which you drag to wipe from the before to the after.
- The after alone, at the drawer’s full width.
You zoom with the scroll wheel or to the actual pixels, pan with the hand tool or by holding Space, and drag a box over any area to comment on it. The comment goes back with the area’s position on the image, and the choice of comparison is kept as a setting.
Recordings of interface changes
Section titled “Recordings of interface changes”When a change is about how the interface behaves over time, such as a spinner or a sequence of steps, a screen can be a recording of the part in use instead, an MP4, MOV or WebM file. The side panel shows its first frame with a play mark, and the drawer plays it, with a scrubber and the time.
Comment on this frame pauses the recording and comments on the frame at the playhead, and a box dragged on the video comments on an area of that frame. Each comment marks the scrubber at its moment, and its box shows while the playhead is there. In the side panel, a recording’s comment names its moment, and choosing it opens the recording there.
Comments and reviewed marks
Section titled “Comments and reviewed marks”- Comments go on a part, an interaction, a line or a range of lines in the drawer, an area of a screenshot, a moment of a recording or an area of its frame, and an entity of a diagram. The field stays at the bottom of the side panel. Badges on the map, the tabs and the files count the comments.
- Reviewed marks are set on each part, interaction or entity you have read, and go back to the agent. The header counts the parts and interactions the change adds, changes or removes that you have reviewed. Clicking the count goes to the next one left.
You choose Approve or Request changes in the header. Until you choose, comments ask for changes, and a review with none approves. With comments and Approve, the hand-over says Approve with comments.
The keys are N and P to move between parts, Enter to open the selection’s first file, C to comment on it, R to mark it reviewed, U to go to the next part or interaction left to review and F to fit the diagram to the view. Esc closes the drawer.
To see it before any agent asks with it, send its sample: pinrail submit change-map --sample, or Send a sample in its details in Settings › Plugins.
Asking from your agent
Section titled “Asking from your agent”The plugin ships a skill that tells an agent how to build a map from a change: how to work out the parts and their interactions, choose the line ranges, add screenshots and decide when a diagram helps, with a script that lists the change’s files from git and one that notes what a map leaves out before it is submitted. Turn it on with the switch under Skill in the plugin’s details in Settings › Plugins. An agent you connected in Settings › Agents then has it in its pinrail skill, and any other agent can read it with pinrail plugins skill change-map. Your instructions only need to say when to use it:
When a change is complex, submit it for review with the `change-map` Pinrail plugin.What the agent sends
Section titled “What the agent sends”{ "change": { "title": "Dedup tickets on save", "source": "fix/ticket-dedup", "target": "main" }, "summary": "A second press of **Save** no longer creates a second ticket.", "lanes": [{ "id": "web", "title": "Web app" }, { "id": "api", "title": "API" }], "nodes": [ { "id": "ticket-form", "title": "Ticket form", "kind": "ui", "lane": "web", "status": "changed", "changes": [{ "file": "web/src/TicketForm.tsx", "lines": [[40, 68]] }], "screens": [{ "title": "Save while saving", "before": { "$attachment": "form-before.png" }, "after": { "$attachment": "form-after.png" } }] }, { "id": "tickets-endpoint", "title": "POST /tickets", "kind": "endpoint", "lane": "api", "changes": [{ "file": "lib/helpdesk_web/controllers/ticket_controller.ex" }] } ], "edges": [{ "id": "post", "from": "ticket-form", "to": "tickets-endpoint", "label": "POST /tickets" }], "files": [{ "path": "web/src/TicketForm.tsx", "status": "modified", "diff": "@@ -38,20 +38,31 @@ …" }]}nodesare the parts of the change. A part’skindsets its icon, itsstatusits colour, and itschangesthe files and line ranges that implement it.edgesare the interactions between parts, such as calls, events or imports.filesholds the unified diff of each changed file. A file that no part or interaction names is listed in the panel as one of the other files.screenshold screenshots of an interface part, before and after, or a recording of it asvideo, each sent with--attachand named as{"$attachment": "<name>"}.diagramsare optional sequence and state diagrams, whose participants and messages can name a part withnodeand an interaction withedge.
What comes back
Section titled “What comes back”{ "verdict": "request_changes", "comments": [ { "target": { "node": "tickets-endpoint" }, "body": "Return the id of the existing ticket with the 409." }, { "target": { "file": "web/src/TicketForm.tsx", "start_line": 47, "line": 49, "side": "new" }, "body": "Keep the key after a failed save." }, { "target": { "diagram": "save-button", "item": "t4" }, "body": "Show why the save failed." } ], "reviewed": ["ticket-form", "post"]}| Field | What the agent does with it |
|---|---|
verdict | approve: goes on, and reads any comments as notes. request_changes: applies the comments in a new version. |
comments[].target | What the comment is about: a part (node), an interaction (edge), lines of a file (file, line, start_line and side), an area of a screenshot (screen and region, in fractions of the image, with pixels and image_size giving the same area in the image’s own pixels), a moment of a recording (screen with the image video, and time in seconds, with an area of its frame as for a screenshot when one is marked), or an entity of a diagram (diagram and item). |
reviewed | The parts, interactions and diagram entities the person marked as reviewed. |
Reference
Section titled “Reference”The plugin’s manifest, and the schemas a payload and a decision are checked against, read from the plugin’s own files.
name | change-map |
|---|---|
version | 1.1.1 |
title | Change map |
description | A code change, such as a pull request, drawn as a diagram of the parts it touches and how they interact. Each part opens its files in a diff viewer, and parts of the interface show screenshots before and after. Comment on a part, an interaction or a line, and approve the change or ask for changes. |
use_when | You made or studied a code change with several moving parts, such as a feature across services, modules and screens, and the person should understand how the parts fit together before reading the diff, then approve it or say what to change. |
attachments | .png .jpg .jpeg .webp .gif image/png image/jpeg image/webp image/gif .mp4 .m4v .mov .webm video/mp4 video/quicktime video/webm · up to 100 MB each, 32 a review |
shortcuts |
|
{ "name": "change-map", "version": "1.1.1", "title": "Change map", "description": "A code change, such as a pull request, drawn as a diagram of the parts it touches and how they interact. Each part opens its files in a diff viewer, and parts of the interface show screenshots before and after. Comment on a part, an interaction or a line, and approve the change or ask for changes.", "use_when": "You made or studied a code change with several moving parts, such as a feature across services, modules and screens, and the person should understand how the parts fit together before reading the diff, then approve it or say what to change.", "settings_schema": { "type": "object", "properties": { "show_diff": { "type": "boolean", "title": "Show the diff drawer", "description": "Open a review with the diff drawer showing, as you left it last.", "default": false }, "card_style": { "type": "string", "title": "Cards", "description": "How the map draws each part: with its kind and status in a header and the lines it changes, or as a compact card.", "oneOf": [ { "const": "detailed", "title": "Detailed" }, { "const": "compact", "title": "Compact" } ], "default": "detailed" }, "state_direction": { "type": "string", "title": "State diagrams", "description": "Which way a state diagram runs from its first state.", "oneOf": [ { "const": "LR", "title": "Left to right" }, { "const": "TB", "title": "Top to bottom" } ], "default": "LR" }, "screen_mode": { "type": "string", "title": "Screenshot comparison", "description": "How a part's screenshots open when it has a before and an after", "oneOf": [ { "const": "side", "title": "Side by side" }, { "const": "wipe", "title": "Slider" }, { "const": "after", "title": "After only" } ], "default": "side" }, "panel_width": { "type": "integer", "title": "Side panel width", "description": "The side panel's width in pixels, as you last dragged it.", "minimum": 340, "maximum": 2000, "default": 340 } } }, "attachments": { "accept": [ ".png", ".jpg", ".jpeg", ".webp", ".gif", "image/png", "image/jpeg", "image/webp", "image/gif", ".mp4", ".m4v", ".mov", ".webm", "video/mp4", "video/quicktime", "video/webm" ], "max_size": 104857600, "max_count": 32 }, "shortcuts": [ { "keys": "n", "does": "Select the next part", "group": "Diagram" }, { "keys": "p", "does": "Select the previous part", "group": "Diagram" }, { "keys": "enter", "does": "Open the selected part's first file", "group": "Diagram" }, { "keys": "c", "does": "Comment on the selection", "group": "Diagram" }, { "keys": "r", "does": "Mark the selection as reviewed", "group": "Diagram" }, { "keys": "u", "does": "Go to the next part or interaction to review", "group": "Diagram" }, { "keys": "f", "does": "Fit the diagram to the frame", "group": "Diagram" } ], "summary": { "outcome": { "verdict": { "at": "/verdict", "values": { "approve": { "label": "approved", "tone": "success" }, "request_changes": { "label": "changes requested", "tone": "warning" } } }, "counts": [ { "items": "/comments", "label": "comment", "plural": "comments", "tone": "info" } ] } }}changeobject
The change under review, such as a pull request.
- refstring
Its reference on the code host, such as #128 or !42.
at most 80 chars
- titlestring
at most 300 chars
- urlstring
Its page on the code host.
at most 2000 chars
- sourcestring
The branch with the change.
at most 200 chars
- targetstring
The branch it goes into.
at most 200 chars
- descriptionstring
Its description, in Markdown.
at most 20000 chars
- summarystring
What the change does and why, in Markdown. Shown before anything is selected.
at most 20000 chars
lanesobject[]
Groups of related parts, drawn as bands, such as the web app, the API and the database.
at most 12 items
- idstringrequired
1–80 chars · matches
^[A-Za-z0-9][A-Za-z0-9_.:-]*$ - titlestringrequired
1–80 chars
nodesobject[]required
The parts of the system the change touches, and parts it does not touch that help explain it.
1–80 items
- idstringrequired
1–80 chars · matches
^[A-Za-z0-9][A-Za-z0-9_.:-]*$ - titlestringrequired
1–120 chars
- kindenum
What the part is: a screen or component, a module, a service, an endpoint, a store such as a table or a queue, a job, a third-party system, configuration or tests. Sets its icon.
"ui""module""service""endpoint""store""job""external""config""test" - lanestring
The id of the lane it belongs to.
at most 80 chars
- statusenum
What the change does to it. context: a part the change does not touch.
"added""changed""removed""context"· default"changed" - summarystring
What changed in this part and why, in Markdown.
at most 20000 chars
changesobject[]
The files that belong to this part, each optionally narrowed to line ranges on the new side.
at most 200 items
- filestringrequired
A path in files.
1–500 chars
- linesarray[]
Ranges of lines on the new side, each [first, last].
at most 50 items
screensobject[]
Screens of a user interface part: screenshots before and after the change, or one of them for a screen that is new or removed, or a recording of the part in use.
at most 12 items
- One of:
beforeorafterorvideo - titlestring
at most 200 chars
- beforefile
- afterfile
- videofile
A recording of the part in use, such as an MP4 or a WebM screen capture, in place of screenshots.
- One of:
edgesobject[]
How the parts interact.
at most 200 items
- idstringrequired
1–80 chars · matches
^[A-Za-z0-9][A-Za-z0-9_.:-]*$ - fromstringrequired
The id of the node it starts from.
at most 80 chars
- tostringrequired
The id of the node it goes to.
at most 80 chars
- labelstring
at most 120 chars
- kindenum
A request and its response, a read or a write, a message or a notification, a code dependency, or a component that draws another.
"call""data""event""import""render" - statusenum
"added""changed""removed""unchanged"· default"changed" - summarystring
What changed in this interaction, in Markdown.
at most 20000 chars
detailobject
The shapes that cross the edge, shown as code.
- requeststring
at most 20000 chars
- responsestring
at most 20000 chars
changesobject[]
The files that belong to this part, each optionally narrowed to line ranges on the new side.
at most 200 items
- filestringrequired
A path in files.
1–500 chars
- linesarray[]
Ranges of lines on the new side, each [first, last].
at most 50 items
diagramsobject[]
Optional diagrams beside the map, for a change about order or lifecycle more than structure. Add one only when the map alone does not explain the change.
at most 12 items
- idstringrequired
1–80 chars · matches
^[A-Za-z0-9][A-Za-z0-9_.:-]*$ - kindenumrequired
sequence: participants and the messages between them, in order. state: states and the transitions between them.
"sequence""state" - titlestringrequired
1–120 chars
- summarystring
What the diagram shows, in Markdown.
at most 20000 chars
participantsobject[]
A sequence's participants, from left to right.
at most 20 items
- idstringrequired
1–80 chars · matches
^[A-Za-z0-9][A-Za-z0-9_.:-]*$ - titlestringrequired
1–120 chars
- kindenum
What the part is: a screen or component, a module, a service, an endpoint, a store such as a table or a queue, a job, a third-party system, configuration or tests. Sets its icon.
"ui""module""service""endpoint""store""job""external""config""test" - nodestring
The map part it is. Comments, files and the reviewed mark are then the part's.
at most 80 chars
- statusenum
What the change does to it. context or unchanged: something the change leaves alone.
"added""changed""removed""context""unchanged"· default"changed" - summarystring
What changed in it and why, in Markdown.
at most 20000 chars
changesobject[]
The files that belong to this part, each optionally narrowed to line ranges on the new side.
at most 200 items
- filestringrequired
A path in files.
1–500 chars
- linesarray[]
Ranges of lines on the new side, each [first, last].
at most 50 items
messagesobject[]
A sequence's messages, from top to bottom.
at most 200 items
- idstringrequired
1–80 chars · matches
^[A-Za-z0-9][A-Za-z0-9_.:-]*$ - fromstringrequired
The id of the participant that sends it.
at most 80 chars
- tostringrequired
The id of the participant that receives it.
at most 80 chars
- labelstring
at most 160 chars
- kindenum
"call""async""reply""self"· default"call" - edgestring
The map interaction it is. Comments, files and the reviewed mark are then the interaction's.
at most 80 chars
- statusenum
What the change does to it. context or unchanged: something the change leaves alone.
"added""changed""removed""context""unchanged"· default"changed" - summarystring
What changed in it and why, in Markdown.
at most 20000 chars
changesobject[]
The files that belong to this part, each optionally narrowed to line ranges on the new side.
at most 200 items
- filestringrequired
A path in files.
1–500 chars
- linesarray[]
Ranges of lines on the new side, each [first, last].
at most 50 items
groupsobject[]
Frames around a run of a sequence's messages.
at most 40 items
- idstringrequired
1–80 chars · matches
^[A-Za-z0-9][A-Za-z0-9_.:-]*$ - kindenumrequired
"alt""opt""loop""par" - labelstring
at most 160 chars
- firststringrequired
The id of the first message in the frame.
at most 80 chars
- laststringrequired
The id of the last message in the frame.
at most 80 chars
statesobject[]
A state diagram's states.
at most 60 items
- idstringrequired
1–80 chars · matches
^[A-Za-z0-9][A-Za-z0-9_.:-]*$ - titlestring
at most 120 chars
- kindenum
"state""initial""final"· default"state" - statusenum
What the change does to it. context or unchanged: something the change leaves alone.
"added""changed""removed""context""unchanged"· default"changed" - summarystring
What changed in it and why, in Markdown.
at most 20000 chars
changesobject[]
The files that belong to this part, each optionally narrowed to line ranges on the new side.
at most 200 items
- filestringrequired
A path in files.
1–500 chars
- linesarray[]
Ranges of lines on the new side, each [first, last].
at most 50 items
transitionsobject[]
A state diagram's transitions.
at most 200 items
- idstringrequired
1–80 chars · matches
^[A-Za-z0-9][A-Za-z0-9_.:-]*$ - fromstringrequired
at most 80 chars
- tostringrequired
at most 80 chars
- labelstring
Its event, guard and action, as you write them.
at most 160 chars
- statusenum
What the change does to it. context or unchanged: something the change leaves alone.
"added""changed""removed""context""unchanged"· default"changed" - summarystring
What changed in it and why, in Markdown.
at most 20000 chars
changesobject[]
The files that belong to this part, each optionally narrowed to line ranges on the new side.
at most 200 items
- filestringrequired
A path in files.
1–500 chars
- linesarray[]
Ranges of lines on the new side, each [first, last].
at most 50 items
filesobject[]required
Every changed file with its unified diff, as git diff prints it.
1–500 items
- pathstringrequired
1–500 chars
- statusenum
"added""modified""deleted""renamed" - wasstring
The old path of a renamed file.
at most 500 chars
- diffstringrequired
at most 2000000 chars
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Change map", "type": "object", "additionalProperties": false, "required": [ "nodes", "files" ], "properties": { "change": { "type": "object", "additionalProperties": false, "description": "The change under review, such as a pull request.", "properties": { "ref": { "type": "string", "maxLength": 80, "description": "Its reference on the code host, such as #128 or !42." }, "title": { "type": "string", "maxLength": 300 }, "url": { "type": "string", "maxLength": 2000, "description": "Its page on the code host." }, "source": { "type": "string", "maxLength": 200, "description": "The branch with the change." }, "target": { "type": "string", "maxLength": 200, "description": "The branch it goes into." }, "description": { "type": "string", "maxLength": 20000, "description": "Its description, in Markdown." } } }, "summary": { "type": "string", "maxLength": 20000, "description": "What the change does and why, in Markdown. Shown before anything is selected." }, "lanes": { "type": "array", "maxItems": 12, "description": "Groups of related parts, drawn as bands, such as the web app, the API and the database.", "items": { "type": "object", "additionalProperties": false, "required": [ "id", "title" ], "properties": { "id": { "type": "string", "minLength": 1, "maxLength": 80, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]*$" }, "title": { "type": "string", "minLength": 1, "maxLength": 80 } } } }, "nodes": { "type": "array", "minItems": 1, "maxItems": 80, "description": "The parts of the system the change touches, and parts it does not touch that help explain it.", "items": { "type": "object", "additionalProperties": false, "required": [ "id", "title" ], "properties": { "id": { "type": "string", "minLength": 1, "maxLength": 80, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]*$" }, "title": { "type": "string", "minLength": 1, "maxLength": 120 }, "kind": { "enum": [ "ui", "module", "service", "endpoint", "store", "job", "external", "config", "test" ], "description": "What the part is: a screen or component, a module, a service, an endpoint, a store such as a table or a queue, a job, a third-party system, configuration or tests. Sets its icon." }, "lane": { "type": "string", "maxLength": 80, "description": "The id of the lane it belongs to." }, "status": { "enum": [ "added", "changed", "removed", "context" ], "default": "changed", "description": "What the change does to it. context: a part the change does not touch." }, "summary": { "type": "string", "maxLength": 20000, "description": "What changed in this part and why, in Markdown." }, "changes": { "type": "array", "maxItems": 200, "description": "The files that belong to this part, each optionally narrowed to line ranges on the new side.", "items": { "type": "object", "additionalProperties": false, "required": [ "file" ], "properties": { "file": { "type": "string", "minLength": 1, "maxLength": 500, "description": "A path in files." }, "lines": { "type": "array", "maxItems": 50, "description": "Ranges of lines on the new side, each [first, last].", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": { "type": "integer", "minimum": 1 } } } } } }, "screens": { "type": "array", "maxItems": 12, "description": "Screens of a user interface part: screenshots before and after the change, or one of them for a screen that is new or removed, or a recording of the part in use.", "items": { "type": "object", "additionalProperties": false, "anyOf": [ { "required": [ "before" ] }, { "required": [ "after" ] }, { "required": [ "video" ] } ], "properties": { "title": { "type": "string", "maxLength": 200 }, "before": { "$ref": "#/$defs/attachment" }, "after": { "$ref": "#/$defs/attachment" }, "video": { "$ref": "#/$defs/attachment", "description": "A recording of the part in use, such as an MP4 or a WebM screen capture, in place of screenshots." } } } } } } }, "edges": { "type": "array", "maxItems": 200, "description": "How the parts interact.", "items": { "type": "object", "additionalProperties": false, "required": [ "id", "from", "to" ], "properties": { "id": { "type": "string", "minLength": 1, "maxLength": 80, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]*$" }, "from": { "type": "string", "maxLength": 80, "description": "The id of the node it starts from." }, "to": { "type": "string", "maxLength": 80, "description": "The id of the node it goes to." }, "label": { "type": "string", "maxLength": 120 }, "kind": { "enum": [ "call", "data", "event", "import", "render" ], "description": "A request and its response, a read or a write, a message or a notification, a code dependency, or a component that draws another." }, "status": { "enum": [ "added", "changed", "removed", "unchanged" ], "default": "changed" }, "summary": { "type": "string", "maxLength": 20000, "description": "What changed in this interaction, in Markdown." }, "detail": { "type": "object", "additionalProperties": false, "description": "The shapes that cross the edge, shown as code.", "properties": { "request": { "type": "string", "maxLength": 20000, "description": "" }, "response": { "type": "string", "maxLength": 20000, "description": "" } } }, "changes": { "type": "array", "maxItems": 200, "description": "The files that belong to this part, each optionally narrowed to line ranges on the new side.", "items": { "type": "object", "additionalProperties": false, "required": [ "file" ], "properties": { "file": { "type": "string", "minLength": 1, "maxLength": 500, "description": "A path in files." }, "lines": { "type": "array", "maxItems": 50, "description": "Ranges of lines on the new side, each [first, last].", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": { "type": "integer", "minimum": 1 } } } } } } } } }, "diagrams": { "type": "array", "maxItems": 12, "description": "Optional diagrams beside the map, for a change about order or lifecycle more than structure. Add one only when the map alone does not explain the change.", "items": { "type": "object", "additionalProperties": false, "required": [ "id", "kind", "title" ], "properties": { "id": { "type": "string", "minLength": 1, "maxLength": 80, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]*$" }, "kind": { "enum": [ "sequence", "state" ], "description": "sequence: participants and the messages between them, in order. state: states and the transitions between them." }, "title": { "type": "string", "minLength": 1, "maxLength": 120 }, "summary": { "type": "string", "maxLength": 20000, "description": "What the diagram shows, in Markdown." }, "participants": { "type": "array", "maxItems": 20, "description": "A sequence's participants, from left to right.", "items": { "type": "object", "additionalProperties": false, "required": [ "id", "title" ], "properties": { "id": { "type": "string", "minLength": 1, "maxLength": 80, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]*$" }, "title": { "type": "string", "minLength": 1, "maxLength": 120 }, "kind": { "enum": [ "ui", "module", "service", "endpoint", "store", "job", "external", "config", "test" ], "description": "What the part is: a screen or component, a module, a service, an endpoint, a store such as a table or a queue, a job, a third-party system, configuration or tests. Sets its icon." }, "node": { "type": "string", "maxLength": 80, "description": "The map part it is. Comments, files and the reviewed mark are then the part's." }, "status": { "enum": [ "added", "changed", "removed", "context", "unchanged" ], "default": "changed", "description": "What the change does to it. context or unchanged: something the change leaves alone." }, "summary": { "type": "string", "maxLength": 20000, "description": "What changed in it and why, in Markdown." }, "changes": { "type": "array", "maxItems": 200, "description": "The files that belong to this part, each optionally narrowed to line ranges on the new side.", "items": { "type": "object", "additionalProperties": false, "required": [ "file" ], "properties": { "file": { "type": "string", "minLength": 1, "maxLength": 500, "description": "A path in files." }, "lines": { "type": "array", "maxItems": 50, "description": "Ranges of lines on the new side, each [first, last].", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": { "type": "integer", "minimum": 1 } } } } } } } } }, "messages": { "type": "array", "maxItems": 200, "description": "A sequence's messages, from top to bottom.", "items": { "type": "object", "additionalProperties": false, "required": [ "id", "from", "to" ], "properties": { "id": { "type": "string", "minLength": 1, "maxLength": 80, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]*$" }, "from": { "type": "string", "maxLength": 80, "description": "The id of the participant that sends it." }, "to": { "type": "string", "maxLength": 80, "description": "The id of the participant that receives it." }, "label": { "type": "string", "maxLength": 160 }, "kind": { "enum": [ "call", "async", "reply", "self" ], "default": "call" }, "edge": { "type": "string", "maxLength": 80, "description": "The map interaction it is. Comments, files and the reviewed mark are then the interaction's." }, "status": { "enum": [ "added", "changed", "removed", "context", "unchanged" ], "default": "changed", "description": "What the change does to it. context or unchanged: something the change leaves alone." }, "summary": { "type": "string", "maxLength": 20000, "description": "What changed in it and why, in Markdown." }, "changes": { "type": "array", "maxItems": 200, "description": "The files that belong to this part, each optionally narrowed to line ranges on the new side.", "items": { "type": "object", "additionalProperties": false, "required": [ "file" ], "properties": { "file": { "type": "string", "minLength": 1, "maxLength": 500, "description": "A path in files." }, "lines": { "type": "array", "maxItems": 50, "description": "Ranges of lines on the new side, each [first, last].", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": { "type": "integer", "minimum": 1 } } } } } } } } }, "groups": { "type": "array", "maxItems": 40, "description": "Frames around a run of a sequence's messages.", "items": { "type": "object", "additionalProperties": false, "required": [ "id", "kind", "first", "last" ], "properties": { "id": { "type": "string", "minLength": 1, "maxLength": 80, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]*$" }, "kind": { "enum": [ "alt", "opt", "loop", "par" ] }, "label": { "type": "string", "maxLength": 160 }, "first": { "type": "string", "maxLength": 80, "description": "The id of the first message in the frame." }, "last": { "type": "string", "maxLength": 80, "description": "The id of the last message in the frame." } } } }, "states": { "type": "array", "maxItems": 60, "description": "A state diagram's states.", "items": { "type": "object", "additionalProperties": false, "required": [ "id" ], "properties": { "id": { "type": "string", "minLength": 1, "maxLength": 80, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]*$" }, "title": { "type": "string", "maxLength": 120 }, "kind": { "enum": [ "state", "initial", "final" ], "default": "state" }, "status": { "enum": [ "added", "changed", "removed", "context", "unchanged" ], "default": "changed", "description": "What the change does to it. context or unchanged: something the change leaves alone." }, "summary": { "type": "string", "maxLength": 20000, "description": "What changed in it and why, in Markdown." }, "changes": { "type": "array", "maxItems": 200, "description": "The files that belong to this part, each optionally narrowed to line ranges on the new side.", "items": { "type": "object", "additionalProperties": false, "required": [ "file" ], "properties": { "file": { "type": "string", "minLength": 1, "maxLength": 500, "description": "A path in files." }, "lines": { "type": "array", "maxItems": 50, "description": "Ranges of lines on the new side, each [first, last].", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": { "type": "integer", "minimum": 1 } } } } } } } } }, "transitions": { "type": "array", "maxItems": 200, "description": "A state diagram's transitions.", "items": { "type": "object", "additionalProperties": false, "required": [ "id", "from", "to" ], "properties": { "id": { "type": "string", "minLength": 1, "maxLength": 80, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]*$" }, "from": { "type": "string", "maxLength": 80 }, "to": { "type": "string", "maxLength": 80 }, "label": { "type": "string", "maxLength": 160, "description": "Its event, guard and action, as you write them." }, "status": { "enum": [ "added", "changed", "removed", "context", "unchanged" ], "default": "changed", "description": "What the change does to it. context or unchanged: something the change leaves alone." }, "summary": { "type": "string", "maxLength": 20000, "description": "What changed in it and why, in Markdown." }, "changes": { "type": "array", "maxItems": 200, "description": "The files that belong to this part, each optionally narrowed to line ranges on the new side.", "items": { "type": "object", "additionalProperties": false, "required": [ "file" ], "properties": { "file": { "type": "string", "minLength": 1, "maxLength": 500, "description": "A path in files." }, "lines": { "type": "array", "maxItems": 50, "description": "Ranges of lines on the new side, each [first, last].", "items": { "type": "array", "minItems": 2, "maxItems": 2, "items": { "type": "integer", "minimum": 1 } } } } } } } } } } } }, "files": { "type": "array", "minItems": 1, "maxItems": 500, "description": "Every changed file with its unified diff, as git diff prints it.", "items": { "type": "object", "additionalProperties": false, "required": [ "path", "diff" ], "properties": { "path": { "type": "string", "minLength": 1, "maxLength": 500 }, "status": { "enum": [ "added", "modified", "deleted", "renamed" ] }, "was": { "type": "string", "maxLength": 500, "description": "The old path of a renamed file." }, "diff": { "type": "string", "maxLength": 2000000 } } } } }, "$defs": { "attachment": { "type": "object", "additionalProperties": false, "required": [ "$attachment" ], "properties": { "$attachment": { "type": "string", "minLength": 1, "maxLength": 120 } } } }}- verdictenumrequired
approve: go ahead with the change. request_changes: change it as the comments say, then send a new round.
"approve""request_changes" commentsobject[]required
at most 500 items
targetany | any | any | any | any | anyrequired
What a comment is on: a node, an edge, a line of a file or lines from start_line to line on the same side, an area of a screenshot, a moment of a recording or an area of its frame, or an entity of an extra diagram.
- One of:
nodeoredgeorfile+lineorscreen+regionorscreen+timeordiagram+item - nodeany
- edgeany
- fileany
- lineany
- sideany
- start_lineany
- screenany
- regionany
- diagramany
- itemany
- pixelsany
- image_sizeany
- timeany
- One of:
- bodystringrequired
1–20000 chars
- reviewedstring[]
The nodes, edges and diagram entities (as <diagram>:<item>) the person marked as reviewed.
at most 300 items
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Change map decision", "type": "object", "additionalProperties": false, "required": [ "verdict", "comments" ], "properties": { "verdict": { "enum": [ "approve", "request_changes" ], "description": "approve: go ahead with the change. request_changes: change it as the comments say, then send a new round." }, "comments": { "type": "array", "maxItems": 500, "items": { "type": "object", "additionalProperties": false, "required": [ "target", "body" ], "properties": { "target": { "type": "object", "additionalProperties": false, "description": "What a comment is on: a node, an edge, a line of a file or lines from start_line to line on the same side, an area of a screenshot, a moment of a recording or an area of its frame, or an entity of an extra diagram.", "oneOf": [ { "required": [ "node" ], "properties": { "node": { "type": "string", "maxLength": 80 } } }, { "required": [ "edge" ], "properties": { "edge": { "type": "string", "maxLength": 80 } } }, { "required": [ "file", "line" ], "properties": { "file": { "type": "string", "maxLength": 500 }, "line": { "type": "integer", "minimum": 1 }, "side": { "enum": [ "old", "new" ], "default": "new" }, "start_line": { "type": "integer", "minimum": 1, "description": "The first line of a range of lines; line is the last." } } }, { "required": [ "screen", "region" ], "properties": { "screen": { "type": "object", "additionalProperties": false, "required": [ "node", "index", "image" ], "description": "The screenshot: a node's screens[index], its before or its after image.", "properties": { "node": { "type": "string", "maxLength": 80 }, "index": { "type": "integer", "minimum": 0 }, "image": { "enum": [ "before", "after" ] } } }, "region": { "type": "object", "additionalProperties": false, "required": [ "x", "y", "width", "height" ], "description": "The area, as fractions of the image's width and height from its top-left corner.", "properties": { "x": { "type": "number", "minimum": 0, "maximum": 1 }, "y": { "type": "number", "minimum": 0, "maximum": 1 }, "width": { "type": "number", "minimum": 0, "maximum": 1 }, "height": { "type": "number", "minimum": 0, "maximum": 1 } } }, "pixels": { "type": "object", "additionalProperties": false, "required": [ "x", "y", "width", "height" ], "description": "The same area in the image's own pixels, from its top-left corner.", "properties": { "x": { "type": "integer", "minimum": 0 }, "y": { "type": "integer", "minimum": 0 }, "width": { "type": "integer", "minimum": 0 }, "height": { "type": "integer", "minimum": 0 } } }, "image_size": { "type": "object", "additionalProperties": false, "required": [ "width", "height" ], "description": "The image's own size in pixels, which `pixels` is in.", "properties": { "width": { "type": "integer", "minimum": 1 }, "height": { "type": "integer", "minimum": 1 } } } } }, { "required": [ "screen", "time" ], "properties": { "screen": { "type": "object", "additionalProperties": false, "required": [ "node", "index", "image" ], "description": "The recording: a node's screens[index], its video.", "properties": { "node": { "type": "string", "maxLength": 80 }, "index": { "type": "integer", "minimum": 0 }, "image": { "const": "video" } } }, "region": { "type": "object", "additionalProperties": false, "required": [ "x", "y", "width", "height" ], "description": "An area of the frame at that moment, as fractions of its width and height from its top-left corner. Without it, the comment is on the whole frame.", "properties": { "x": { "type": "number", "minimum": 0, "maximum": 1 }, "y": { "type": "number", "minimum": 0, "maximum": 1 }, "width": { "type": "number", "minimum": 0, "maximum": 1 }, "height": { "type": "number", "minimum": 0, "maximum": 1 } } }, "pixels": { "type": "object", "additionalProperties": false, "required": [ "x", "y", "width", "height" ], "description": "The same area in the video's own pixels, from its top-left corner.", "properties": { "x": { "type": "integer", "minimum": 0 }, "y": { "type": "integer", "minimum": 0 }, "width": { "type": "integer", "minimum": 0 }, "height": { "type": "integer", "minimum": 0 } } }, "image_size": { "type": "object", "additionalProperties": false, "required": [ "width", "height" ], "description": "The video's own size in pixels, which `pixels` is in.", "properties": { "width": { "type": "integer", "minimum": 1 }, "height": { "type": "integer", "minimum": 1 } } }, "time": { "type": "number", "minimum": 0, "description": "The moment of the recording, in seconds from its start." } } }, { "required": [ "diagram", "item" ], "properties": { "diagram": { "type": "string", "maxLength": 80 }, "item": { "type": "string", "maxLength": 80 } } } ], "properties": { "node": {}, "edge": {}, "file": {}, "line": {}, "side": {}, "start_line": {}, "screen": {}, "region": {}, "diagram": {}, "item": {}, "pixels": {}, "image_size": {}, "time": {} } }, "body": { "type": "string", "minLength": 1, "maxLength": 20000 } } } }, "reviewed": { "type": "array", "maxItems": 300, "description": "The nodes, edges and diagram entities (as <diagram>:<item>) the person marked as reviewed.", "items": { "type": "string", "maxLength": 161 } } }}- show_diffbooleanShow the diff drawer
Open a review with the diff drawer showing, as you left it last.
default
false - card_stylestringCards
How the map draws each part: with its kind and status in a header and the lines it changes, or as a compact card.
"detailed"Detailed ·"compact"Compact · default"detailed" - state_directionstringState diagrams
Which way a state diagram runs from its first state.
"LR"Left to right ·"TB"Top to bottom · default"LR" - screen_modestringScreenshot comparison
How a part's screenshots open when it has a before and an after
"side"Side by side ·"wipe"Slider ·"after"After only · default"side" - panel_widthintegerSide panel width
The side panel's width in pixels, as you last dragged it.
340–2000 · default
340
{ "type": "object", "properties": { "show_diff": { "type": "boolean", "title": "Show the diff drawer", "description": "Open a review with the diff drawer showing, as you left it last.", "default": false }, "card_style": { "type": "string", "title": "Cards", "description": "How the map draws each part: with its kind and status in a header and the lines it changes, or as a compact card.", "oneOf": [ { "const": "detailed", "title": "Detailed" }, { "const": "compact", "title": "Compact" } ], "default": "detailed" }, "state_direction": { "type": "string", "title": "State diagrams", "description": "Which way a state diagram runs from its first state.", "oneOf": [ { "const": "LR", "title": "Left to right" }, { "const": "TB", "title": "Top to bottom" } ], "default": "LR" }, "screen_mode": { "type": "string", "title": "Screenshot comparison", "description": "How a part's screenshots open when it has a before and an after", "oneOf": [ { "const": "side", "title": "Side by side" }, { "const": "wipe", "title": "Slider" }, { "const": "after", "title": "After only" } ], "default": "side" }, "panel_width": { "type": "integer", "title": "Side panel width", "description": "The side panel's width in pixels, as you last dragged it.", "minimum": 340, "maximum": 2000, "default": 340 } }}