Skip to content
azihsoynPublic

About

An infinite canvas in your terminal: boxes, arrows and tables drawn with the mouse. Obsidian Canvas compatible, ASCII export, JSON API + CRDT for live human/AI co-editing

Topics

Resources

Stars

7 stars

Watchers

0 watching

Forks

Repository files navigation

ratslate

An infinite canvas in the terminal. Boxes, arrows and text, placed and moved with the mouse — and written out as ASCII, so the drawing can live in a README or a comment rather than only on the screen it was made on.

demo

Reads and writes JSON Canvas (.canvas), the format Obsidian's Canvas uses, so a board can round-trip through Obsidian — negative coordinates, groups, file and link cards included, every coordinate saved back exactly where the other tool put it.

brew install azihsoyn/tap/ratslate   # Homebrew (macOS/Linux)
cargo install ratslate               # or from crates.io
ratslate board.canvas                # then: drag empty space to draw a box

Needs a terminal with mouse reporting (most modern ones: iTerm2, kitty, Alacritty, WezTerm, GNOME Terminal, Windows Terminal, tmux).

What's on a board

  • Boxes — drag empty canvas to place one, type into it, double-click to edit. c cycles colors, or use the ● button for a real color picker — a truecolor gradient field with a hue strip and live preview, plus the six JSON Canvas presets — border styles: plain, rounded, thick, double or dashed (x cycles them too) — and how the color is applied: outline and text only, a tinted background, or solid (f cycles; text goes black or white to stay readable).
  • Connectors — shift+drag from one box to another. Sides, arrowheads, colors, labels, line styles (solid ──, rounded bends ╭╯, thick ━━, double ══, dashed ╌╌) and arrowhead glyphs (> ▶ ▷ ● ◆) all editable; JSON Canvas fromSide/toSide/fromEnd/toEnd respected.
  • Tables — a box whose text is a GFM markdown table renders as a real grid. Double-click a cell to edit it spreadsheet-style (Tab/Enter/arrows, +col -col +row -row buttons, Alt+Enter for a line break, Ctrl+Z inside the session). Still plain markdown in the file — Obsidian renders it as a table. Connectors can anchor to a table's row, column or single cell, and stay anchored when things move.
  • Groups — draw a box around things, press g. Moving the fence moves everything inside it.
  • Stacking — boxes are opaque and stack in JSON Canvas node order. [/] move a selected box one step back/forward, {/} all the way; the API spells it reorder. The order round-trips through Obsidian unchanged.
  • Duplicate — D clones the selection (a single box or a whole multi-selection), copies landing just offset from the originals and already selected to drag away. A connector between two copied boxes comes along.
  • Files & links — JSON Canvas file/link cards: o opens them (creating the file if it doesn't exist yet), y copies the path or URL.
  • Images — a file card pointing at an image (.png, .jpg, .gif, .webp, .bmp) shows the actual picture, scaled to the box and moved or resized with the mouse like anything else — the same image cards an Obsidian canvas has. Needs a terminal with image support (kitty graphics or compatible: kitty, Ghostty, WezTerm, recent iTerm2); elsewhere the card falls back to a labeled box. Drop one on a board with --api '{"type":"set_file","id":"…","file":"photo.png"}', or just open a canvas that already has image cards.

Selecting a box lights up every connector wired to it, and selecting a connector lights up the two boxes it joins — so a board with many crossing lines stays legible: click a thing to see what it's tied to.

The canvas is an infinite plane: pan with arrow keys or the mouse wheel, and the minimap in the corner (toggle m) shows where everything is — click or drag it to jump. Press / to search: type to filter boxes by their text and the view jumps to the first match, Enter steps through the rest.

Press l (or send {"type":"layout"} through --api) for auto-layout: a layered left-to-right arrangement of whatever's connected to what — so an agent can pour in nodes and edges without computing a single coordinate, and a hand-drawn board can untangle itself. One undo step.

Import a graph

ratslate board.canvas --import graph.mmd    # a Mermaid flowchart
ratslate board.canvas --import graph.dot    # or a Graphviz DOT digraph
cat graph.mmd | ratslate board.canvas --import -

A Mermaid flowchart (graph TD, flowchart LR, A -->|label| B, node shapes) or a DOT digraph (A -> B [label="…"]) becomes an editable board, laid out with the same pass the l key runs. So a graph that already lives as text — in a doc, a README, an agent's output — opens as boxes you can nudge with the mouse, then goes back out as --render ASCII or a JSON Canvas for Obsidian.

The --import flag is sugar for one request: an agent can paste a graph straight through the API with --api '{"type":"import","text":"graph TD\n A-->B"}'.

Tabs: several boards, one camera

ratslate before.canvas after.canvas

Multiple files open as tabs sharing a single camera — flip with Tab/Shift+Tab (or click the bar) and the whole board swaps under an unmoving view, so two versions drawn at the same coordinates compare like frames of a flip-book. T clones the active board into the next free <name>-2.canvas to start an "after" from a finished "before". Every file stays a plain, standalone JSON Canvas.

Written out as ASCII

ratslate board.canvas --render

prints the whole board — tables, connectors, groups — exactly as the TUI draws it, ready to paste into a doc, a PR, or a commit message.

--api

Every mouse drag and keystroke is also a Request value, and there is exactly one function that applies one: dispatch. --api is that same function reached from the command line instead of a terminal — not a second implementation of what a move or an edit means.

ratslate board.canvas --schema     # what a request/response looks like
ratslate board.canvas --api '{"type":"place","x":2,"y":2}'
ratslate board.canvas --api '[{"type":"set_text","id":"n1a2b3c4d","text":"hello"},{"type":"save"}]'
ratslate board.canvas --api '{"type":"render"}'   # the ASCII render, as JSON
ratslate board.canvas --api '{"type":"set_status","id":"n1a2b3c4d","status":"running"}'   # ⠋ on the box; "ok" ✓, "failed" ✗, null clears

A JSON array runs as a batch, applied in order, replied to in order. Nothing is written to disk until a save request says so — --api is safe to use for a read-only state query.

Watch a source

ratslate board.canvas --watch 'some-command' --every 5

runs the command on a timer and reads its stdout as a sync document: a list of things to show, each with a stable key, and the links between them. Ratslate matches items to boxes by key and updates them in place — a box keeps wherever you dragged it when only its text or status changed, a new key becomes a new box (and the board re-lays out), a key that's gone is pruned if the document says so. Boxes you placed by hand, which have no key, are never touched. Each document is one sync request through --api, so a script can send the same thing by hand, and the exact schema is in --schema under SyncDoc.

{
  "items": [
    {"key": "job:build", "text": "build\n21s", "status": "ok"},
    {"key": "job:test",  "text": "test",       "status": "running",
     "color": "4", "fill": "tint", "shape": "rounded", "width": 24, "height": 4}
  ],
  "links": [
    {"from": "job:build", "to": "job:test", "label": "needs",
     "style": "dashed", "arrow": "triangle", "color": "2"}
  ],
  "layout": "auto",
  "prune": true
}

Only key is required; an omitted field leaves the box as it was, so a source that knows only statuses can send just keys and statuses. A box's key is saved as ratslateKey — an extension key other JSON Canvas readers ignore — so a watch picks the same boxes back up tomorrow. layout is auto (lay out when the set of keys changes) or keep (never move anything; new boxes land below the board). prune is off by default, so a partial document is safe.

examples/gh-actions-board.py is a watch source: it prints a GitHub Actions run as a document — jobs as items, needs: as links, each job's status as its badge — and ratslate ci.canvas --watch examples/gh-actions-board.py --every 8 draws the run live as it goes. It's a stateless script that prints JSON, which is the point — see examples/README.md.

Ratslate's own emphasis color — selection, related connectors, the flow pulse, the spinner — is cyan; if that sinks into your palette, set RATSLATE_ACCENT=#ffd75f (any hex) and everything emphasized uses that.

Boards as launchers

Set RATSLATE_OPENER to any command and o hands it what's selected — a box's text, a file's path, a link's URL, or the exact table cell under the cursor. The opener decides what "open" means: jump to a terminal pane, open a ticket, anything nameable in text. Unset, o opens file and link boxes through the OS as usual.

Humans and agents, live

Edits sync through a CRDT sidecar (.canvas.crdt, yrs): a person dragging boxes in the TUI and an agent placing them through --api merge live, field by field, instead of overwriting each other's saves. The .canvas file itself stays clean JSON Canvas.

And if the .canvas is edited by another tool entirely — Obsidian, or a script rewriting the file — ratslate notices and reloads it live, so the board on screen keeps up with the file on disk.

A few things move so the board reads at a glance. Press a and a pulse travels along every connector toward its arrowhead, so direction is obvious even on a dense board. An agent can mark the box it's on with set_status — running puts a spinner in the box's top border, ok a check, failed a cross. And a box another writer just changed flashes for a moment, so you see what the agent did and where. None of this costs anything while nothing is moving: the screen only redraws on a timer while something animates.

Install

brew install azihsoyn/tap/ratslate   # Homebrew (macOS/Linux)
cargo install ratslate               # or from crates.io

License

MIT or Apache-2.0, at your option.

About

An infinite canvas in your terminal: boxes, arrows and tables drawn with the mouse. Obsidian Canvas compatible, ASCII export, JSON API + CRDT for live human/AI co-editing

Topics

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages