-
Notifications
You must be signed in to change notification settings - Fork 0
Canvas reference
The page to keep open while you work: every key, what the panels do, how selection behaves, which handle starts which kind of link, and what undo actually covers.
The application ships a shortcut sheet under the ? key, rendered from the table in frontend/src/shortcuts.ts. That file only describes: the listeners live in the components that watch the keyboard, and nothing derives them from the table. The sheet and the table can never disagree with each other, but neither is tied to the handlers. This page carries more than the sheet: the keys the sheet leaves out (? itself, the canvas search bar, the dialog Esc), the scope of each key, the cases where a key deliberately does nothing, and the reasoning behind the layout.
What STIX objects and relationships mean is not here. That lives in the in-app guide: https://app.drawmeastix.io/guide
The listeners test ctrlKey || metaKey, so on macOS Cmd works everywhere Ctrl is written below. Only the display changes.
| Keys | What it does |
|---|---|
Ctrl K
|
Open or close the command palette |
Ctrl Z
|
Undo the last deletion |
Ctrl B
|
Fold or unfold the left panel |
Ctrl+K toggles: pressing it again on an open palette closes it.
Ctrl+B folds whichever left panel is showing. If ATT&CK or Scenarios was open, the first press folds it and the second opens Objects, because the unfold target is always Objects.
| Keys | What it does |
|---|---|
/ |
Open the canvas search bar |
? |
Open or close the shortcut sheet |
Del |
Delete the selection |
Ctrl click or Shift click
|
Add an object to the selection |
| drag | Start a relationship from an object's bottom handle onto another object |
Ctrl V
|
Paste a screenshot from the clipboard as a capture pinned on the canvas |
Del only. Backspace is not bound (deleteKeyCode={['Delete']}), which is deliberate on a canvas that sits one tab away from text entry.
Both Ctrl+click and Shift+click are wired for multi-selection because some window managers swallow Ctrl+click before the page ever sees it (this was seen under Hyprland). Shift+click always gets through.
? is matched on the character produced, not on a physical key code, so it works the same on AZERTY and QWERTY.
| Keys | What it does |
|---|---|
Enter |
Jump to the next hit and centre on it |
Shift Enter
|
Jump to the previous hit |
Esc |
Close the search and clear it |
The search matches an entity's name, its type label, its labels, its aliases, and the content of pinned notes. Matching objects keep their colour, the rest are dimmed. The counter reads 3 / 12, current position over total, because on a dense graph how many are left matters as much as how many there are.
The tray sits at the bottom of the canvas and takes these keys only when it holds focus. Click it once to give it focus.
| Keys | What it does |
|---|---|
j or ↓
|
Next candidate |
k or ↑
|
Previous candidate |
y or Enter
|
Accept: the object goes on the canvas |
n or Del
|
Reject: the object is dropped |
The keys are bound to the panel and not to the window on purpose: an n listened for globally would reject a candidate while you were working on the canvas.
The cursor does not move when you accept or reject. The next candidate slides into its place, so you can hold y down to clear a batch. Full walkthrough: Triage-walkthrough.
| Keys | What it does |
|---|---|
↑ ↓
|
Move through the list (it wraps at both ends) |
Enter |
Run the highlighted line |
Esc |
Step back one stage, then close |
In relate mode Esc drops the target stage, then the source, then closes. Picking the wrong source should not force you to start over from a closed palette.
Every dialog closes on Esc and gives focus back to whatever opened it. Two dialogs can be stacked, because the command palette is not one of them: Ctrl+K still fires over an open dialog, and the action you run from it opens a second box on top. One Esc closes only the topmost.
This is the part a cheat sheet cannot carry, and the part that looks like a bug when you do not know it.
| Situation |
Ctrl K
|
Ctrl B
|
Ctrl Z
|
/ and ?
|
|---|---|---|---|---|
| Caret in an input, textarea or contenteditable | fires | fires | ignored | ignored |
| A dialog is open | fires | fires | fires unless the caret is in a field | ignored |
Ctrl+Z stands down inside a field so that it stays the browser's own undo of typing. Undoing a deletion while fixing a typo is not what anyone means.
/ and ? stand down under a dialog because they used to fire anyway: search opened behind an opaque backdrop, and the cheat sheet landed on top of a half-filled form.
Ctrl+K is the exception that is meant to be one. It is the only route back to the index of everything, so it stays reachable wherever the caret is.
Ctrl+K opens a single index over the canvas objects, object creation, ATT&CK, scenarios, relationships and actions. Before it existed, every capability lived behind its own panel, so you had to know where to look before you could look.
The filter is a subsequence match: crx finds Corax, expc2 finds Exfiltration Over C2 Channel. It indexes names and identifiers only, never prose. When scenario descriptions were indexed, corax surfaced four unrelated scenarios, because in a long text almost any short sequence appears in order.
Groups, in order:
| Group | Holds | Cap |
|---|---|---|
| On this canvas | Every object on the canvas, jump and select | 6 |
| Create | One line per SDO then SCO type | 6 |
| ATT&CK | Techniques and the rest of the dataset, from 2 characters typed | 6 |
| Scenarios | The built-in scenario templates | 6 |
| Relationships | A single entry, reading Relate two objects… until something is selected |
6 |
| Actions | The list below | 6 |
Six per group is a readability cap, not a completeness one: type more to narrow.
The ATT&CK dataset is several hundred entries and is fetched on the first open of the palette, never at startup. The palette must open instantly, offline included, even if that means having no techniques yet in that first second. If the fetch fails, the palette works without them and says nothing.
Pick that entry and the palette turns into a two-step picker: source, then target. Whatever is already selected on the canvas is used as the source, one object or ten, so the common case saves a step.
At the target step, the palette lists only objects that can really be linked, in either direction, plus those reachable through a canonical bridge (single source only). Offering an impossible target helps no one: you pick it, and you get a refusal. When the list comes back empty with no search typed, it says so plainly: no object on this canvas can be linked to that selection, in either direction.
Once source and target are picked, the verb dialog opens, exactly as it does for a mouse drag. The matrix and the bridge fallback live in one place, so keyboard and mouse never validate different things.
| Action | Hint shown |
|---|---|
| Export STIX bundle… | export |
| Export image / PDF / Markdown… | share |
| Paste IOCs… | import |
| Re-layout the graph | canvas |
| Toggle the objects panel | Ctrl B |
| Toggle the inspector | panel |
| Undo the last deletion | Ctrl Z |
| Search the canvas | / |
| Keyboard shortcuts | ? |
| STIX guide: what links to what | help |
| Your data: what stays here, what leaves | help |
| Enrichment endpoints… | settings |
| Back to investigations | navigate |
The two help entries are the ones that open a new tab: the STIX guide on the #/guide route, and https://app.drawmeastix.io/about. Enrichment endpoints… opens a dialog in place, and Back to investigations takes you to the investigation list. Endpoint configuration is covered by Enrichment-sidecar, export by Exporting.
The name field of an object offers what it can from two characters typed, from three corpora that never overlap. Nothing is ever filled in for you: a suggestion is picked or ignored.
| Object being named | What is offered | Where it comes from |
|---|---|---|
| Intrusion set, threat actor | ATT&CK groups, then about 850 more adversary names | ATT&CK Enterprise, plus the MISP threat-actor galaxy |
| Malware, tool, technique | The matching ATT&CK entries | ATT&CK Enterprise |
| Location | The ISO 3166-1 countries, by name or by code | the iso-codes standard |
| Everything else | Nothing, it is a plain field |
The right-hand column of the list names the source: a MITRE identifier (G0016, T1566) for an ATT&CK entry, misp for a name MITRE does not carry, the two-letter code for a country. That is not decoration. Picking an ATT&CK entry writes a mitre-attack external reference into the exported bundle; picking a misp one writes none, because there is no MITRE number to point at and inventing one would be a lie in somebody else's platform.
Why countries are worth a list of their own. A location identifier is computed from its name, so FR, France and French Republic are three different objects that will never merge, here or in the platform that reads the bundle. Type FR, pick France, and the country code and the type are filled in with it.
Why the actor list stops where it does. MITRE and the MISP galaxy disagree about where an actor ends: MITRE folds UNC2452 into APT29, the galaxy keeps it separate. Offering both would hand you two canonical names for one adversary. So every galaxy actor whose name ATT&CK already resolves is dropped when the dataset is built, and ATT&CK wins. Typing Midnight Blizzard offers you APT29, as it should.
The corpora are fetched on demand, never at startup, and a fetch that fails leaves the field working as a plain text input without saying anything.
A scenario slot holds as many values as your case has. The + next to a field label adds a line, Enter in a line does the same, and each line becomes its own object: three tools give three uses, two C2 addresses give two communicates-with. Typing the same name twice in one slot creates it once, since both would carry the same identifier.
The line under the form is always there, in one of three states. It tells you what will come out unlinked, and which empty slot would connect it if you filled it in. It also tells you when a relationship was not drawn: when both ends of one hold several values, two domains and three addresses do not say which resolves to which, and the six links of every combination would be five false statements and one true one. Draw that pairing yourself on the canvas.
| Element | Width | How it opens and closes |
|---|---|---|
| Rail | 44 px | Always visible |
| Left panel: Objects, ATT&CK, Scenarios | 180 px | One at a time. Click a rail icon to switch, click the active icon to fold. Ctrl+B folds and unfolds |
| Right column: Inspector and Narrative tabs | 300 px | The topbar panel icon, the chevron in the tab bar, or the palette action |
| Minimap | 200 px | Rendered only while the window is at least 1200 px wide |
| Triage tray | bottom strip | Rail tray button, or the tray header. Hidden entirely when there are no candidates |
The rail's tray button carries the candidate count as a badge and is disabled at zero, so the curation step announces itself instead of existing only once it is full.
The canvas is the product, and it had ended up at 39 % of the screen on a 1280 px laptop. The thresholds are not arbitrary steps, they follow from one rule: chrome must not exceed 40 % of the window. The constants derive from it, so they track any width change.
| Constant | Value | Meaning |
|---|---|---|
RIGHT_BREAKPOINT |
1310 px | Below this, the right column starts collapsed |
LEFT_BREAKPOINT |
560 px | Below this, the left panel starts collapsed too |
MINIMAP_BREAKPOINT |
1200 px | Below this, no minimap |
One consequence worth knowing, because it looks like the left rule is broken: 560 px is also the width at and under which the application shows its small-screen card instead of the canvas. So unless you took the "Open it anyway" door described below, the left panel starts open on every window you can reach the canvas from, and only the right column folds by itself. It folds on a 1280 px laptop. Behind the opt-in the left rule does apply: under 560 px the left panel starts collapsed.
The minimap is dropped below 1200 px for the reason it exists: at 200 px wide, on an already narrow canvas, it eats the very space it is meant to help you travel.
Whatever you fold or unfold is written to localStorage under dmas.panels and beats the width rule from then on, permanently. Without that memory the interface would undo on every reload what you had just set, which is the worst failing an adaptive layout can have: feeling like it is fighting you.
If storage is unavailable (private browsing, quota), the width decides and your choice holds for the session only.
One exception the layout makes on its own: selecting anything reopens the right column and switches it to the Inspector tab. That is what makes the automatic folding bearable. On a narrow screen you navigate with the whole canvas, and the panel comes back exactly when you ask to see a record. It switches tab too, because selecting an object is asking to see that object, not to re-read the narrative.
| Gesture | Result |
|---|---|
| Click an object | It becomes the single selection, the Inspector shows its record |
Ctrl+click or Shift+click another |
Both are selected |
| Click a relationship | The Inspector shows the verb, the description and the activity window, with a delete button |
| Click a dashed annotation link | The Inspector shows an Unlink panel |
| Click a pinned note card | The Inspector shows the note in full |
With more than one entity selected, the Inspector is replaced by the bulk editor, which applies one patch to the whole batch. Non-entity nodes (pinned notes, captures) are filtered out of that batch: only entities go into it.
Multi-selection is also the input to Relate the N selected objects to… in the palette, and to Del.
A caveat that bites in a second tab: if another tab writes to the same data, a banner appears saying what you see may be out of date. A bulk edit made after that would write back properties read before the change. The banner offers a Reload. Nothing merges automatically, and that is a decision, not an omission: the product is single-user.
An entity node carries four handles. Two of them build STIX, two of them build annotation, and nothing crosses over.
| Handle | Position | What a drag from it starts | Ends up in the bundle |
|---|---|---|---|
| Target | Top edge | Nothing. It receives a relationship | yes |
| Source | Bottom edge | A STIX relationship. The verb dialog opens | yes |
| Annotation | Right edge | An annotation link, and only to a capture | no |
STIX flows top to bottom, the same axis the Re-layout button arranges the graph along, so an edge that points upwards on the canvas is telling you something.
The annotation handle is a pair of overlapping handles, one in each direction, so a capture can be linked from either end. onConnect drops any STIX relationship that would start from there, which is why you cannot accidentally create a relationship by aiming at the wrong edge.
Capture nodes (a screenshot pasted with Ctrl+V) carry their pair of annotation handles on their left edge. Drag from there onto an entity, or from an entity's right handle onto the capture. Double-click a capture to open it full screen.
Pinned note cards carry a handle on the left too, but it is not connectable. A note's dashed link follows the entity the note belongs to; it is not something you draw, and it cannot be deleted on its own.
The two kinds of edge are drawn so they cannot be confused: a STIX relationship is a solid line with an arrowhead and a verb label, an annotation link is dashed, unarrowed, and labelled annotation at most. Annotation links never reach the bundle, see Exporting.
The direction of your drag is not the direction of the relationship. The matrix decides.
- It tries your direction: are there verbs valid from the source type to the target type?
- If not, it tries the reverse.
- If neither works and you dragged a single object, it looks for a canonical bridge and offers the intermediate object instead of failing.
- If nothing holds, it says so: no valid STIX relationship between these two types, in either direction.
This exists so you can think "link these IPs to this infrastructure" without knowing that an observable is never the source of a relationship towards an SDO, and that the correct reading is infrastructure consists-of ip.
For a batch, only the verbs valid for every type in the selection are offered. A mixed batch with no verb in common will tell you so rather than create half the links.
Del on the selection, the small button at a node's top right, or the delete button in the Inspector: all three take exactly the same path, so cleanup and undo behave identically whichever you use.
That top-right button is understated at rest and shows itself when the node is hovered, when it is selected, and when it takes keyboard focus. The last case is not cosmetic: at opacity 0 it was still in the tab order, so keyboard navigation could delete an entity with nothing on screen announcing it.
What each one removes:
| Node kind | What Del does |
|---|---|
| Entity | Deletes the entity, its relationships and its notes |
| Capture | Deletes the capture |
| Pinned note | Unpins it from the canvas. The note itself stays in the notes panel; deleting it for real is done from there |
The note card's button is a minus and not a cross for that reason: it takes the card off the canvas, it does not destroy the note.
Ctrl+Z undoes deletions, and nothing else. That is a decision: a typo in a name gets retyped, but three quarters of an hour of linking does not come back on its own. Property edits, renames and creations are not on the stack.
| Property | Value |
|---|---|
| Depth | 25 entries, oldest dropped |
| Coalescing window | 400 ms |
| Restores | Entities (with their properties), relationships, deleted notes |
| Fallback | With an empty stack, Ctrl+Z restores the positions from before the last Re-layout |
| Empty | Says "Nothing to undo." |
The coalescing window is the detail that makes it usable. Deleting a node makes its edges vanish, and the canvas library reports the edges and the node as two separate events. Without coalescing, one gesture would need two Ctrl+Z, and the first would leave a node stripped of every relationship, which is worse than no undo at all. Everything pushed within 400 ms of the previous entry is undone as one block.
Order matters on restore: entities come back first, then relationships, then notes. A relationship whose endpoint is gone cannot be restored, and you get told how many were dropped rather than a silent partial result.
The entry is only popped once the restores have gone through, so a failure leaves you able to retry. Restores write back on the original identifier, so pressing Ctrl+Z twice during a slow restore breaks nothing.
Re-layout has its own Undo button next to the Re-layout button, and it appears only when there is a layout to go back to. Ctrl+Z falls back to it when the deletion stack is empty, because a Ctrl+Z that does nothing while an Undo button sits on screen is not defensible.
At 560 px wide and under, or on a coarse pointer at 820 px and under, the application shows a presentation card rather than the canvas. There is an "Open it anyway" button, and taking it is remembered under dmas.smallscreen, so a tablet with a keyboard is not asked twice.
The canvas genuinely needs a mouse and a keyboard: relating is a drag, and multi-selection is a modifier click.
- Triage-walkthrough for the tray, from import to accepted objects
- Exporting for what ends up in the bundle and what stays behind
- Troubleshooting when a key or a panel does not behave as described here
- How-it-is-built for where these components live in the source
Using it
Running it
Changing it
When it breaks
In the application:
In the repository: