All notable changes to Hakushi are documented in this file.
The format follows Keep a Changelog, and Hakushi follows Semantic Versioning.
Unreleased
Added
- The Sumi theme follows Sumi's seven themes and their accents: a colour
of the kits of the accent's hue (a pen, a shape, a note, the primary
keys) is another in that theme, so that a selection shows.
mix devshows each (?sumi=smoke), and every colour of the kits at/colours. See the Theming guide. --hakushi-note-<colour>and--hakushi-note-tint, the colours of the notes;--hakushi-kit-alt, Schema's primary keys and Flow's conditions.
Fixed
- The Sumi theme gave Sumi's dark themes other than Ink the light palette and the light colours of the pens and shapes, and Sumi's light themes other than Paper the dark palette on a system in dark mode. The notes on Smoke, and the primary keys on the light themes, were under 4.5:1.
[0.6.0] - 2026-10-24
Added
Hakushi.Kit.group?/1: a kit's node types can be groups (a lane), as a:nodeslot'sgroupmakes them. See the Groups guide.
Fixed
- The minimap kept the colors of the theme the page had when the canvas opened: on a page whose theme changed (a theme picker, the system's dark mode), its nodes and its view were in the old theme's colors, the nodes all but invisible on a dark one. It reads them again when the theme of the page changes.
0.5.0 - 2026-10-23
Diagrams of data: ways to tidy up, a canvas people only arrange, laid out as it opens and at a zoom people read, and a Schema kit that understands the types and schemas of a database.
Added
- Ways to tidy up: in layers (
:right,:down), spread out by forces (:force), or in a grid (:grid).layouttakes them, as a kit's preference does, andHakushi.Live.layout/2takesalgorithm:. A menu by the tidy up button lets people choose, for the page. readonly={:arrange}: people move the nodes, tidy them up and undo that, and change nothing else; for a diagram made from data.tidy: the canvas lays its nodes out as it opens, before people see them, for a graph with no places of its own.fit={:readable}: a large canvas opens at a zoom people read, on its most joined node, rather than on all of it.- Schema: a column's handles are of its family of types
(
Hakushi.Kits.Schema.family/1), sointegerjoinsbigintandvarchar(64)joinstext; the app adds families in its config. - Schema: stubs of tables of another schema (
"external" => true, and"schema"), drawn as ghosts with their qualified name, their edges dashed, and never edited. A node whose content hasdata-hakushi-fainthas its edges drawn so, in any kit.
Changed
Tidy up packs the parts of a graph that no edge joins in rows, in a box of about the screen's shape, and puts the nodes that no edge joins together in a grid, where it laid them all out in one line; a layer far too long for the screen wraps in columns.
The Images kit takes Storehouse 0.2 (
{:storehouse, "~> 0.2"}): its files are checked and stored as before, with Storehouse's 0.2 security changes, and Storehouse now makes its requests with Req.
0.4.0 - 2026-10-23
Install with one command (mix igniter.install hakushi), on Issho 0.4,
with the Ink, Shapes and Images kits, and paper that people choose.
Changed (breaking)
- Requires
{:issho, "~> 0.4.2"}for shared canvases. A canvas's code stays as it was; see the Upgrading guide.
Added
mix hakushi.installruns on Igniter when the app has it, somix igniter.install hakushiadds the dependency and sets up the app in one command. Igniter shows each change and asks before it writes. With Igniter, the task also importsHakushi.Componentsin thehtml_helpersof the web module. Without Igniter, the task works as before. Igniter is an optional dependency.A Features guide: everything the canvas does, in one table.
The events of a shared canvas: each change is an event of its room, named for what it did (
"canvas.added","canvas.connected","canvas.reconnected","canvas.moved","canvas.updated","canvas.deleted"), with the ids of its nodes and edges, and who made it (Issho's document:events). Agents wait for what people do on a canvas (wait_for_eventswith"canvas.*";read_canvassays so), components hear them (Issho.Live.subscribe/3's:events), and apps act on them (Issho.Events.subscribe/2). See "Events" in the Collaboration guide.Comments on a canvas:
Hakushi.Issho.Place.label/3says a place of the canvas in words, from its graph (The node “customers”,The node “customers” and 2 more,A point on the canvas), for the:placeslot of Issho's comments (Issho.Components.comments/1), in the canvas's languages (placeNode,placeNodes,placePoint). The development app's schema has the comments beside it (?comments=1).Images (
Hakushi.Kits.Images): image files dropped on the paper, pasted, or picked with the kit's tool (I), each a node where it lands, at its aspect ratio, sized to fit the view. A placeholder shows the file and its progress while it uploads, to the others too; the node is made once the server has the file and has checked it (PNG, JPEG, GIF or WebP, by its bytes;Hakushi.Images). An image's address pasted is fetched from the hosts the app allows (fetch). Images resize with their aspect ratio (Shift frees it), are cropped in place, take a caption on a double click and alt text from the panel, show a smaller copy at small zooms (thumbnail) and load as they come near; the export draws them.The images are checked, stored and served with Storehouse, as Kotoba's attachments are: the local disk, S3, or a store of the app's (
config :storehouse, adapter: ...);Storehouse.Local.Plugserves them. Storehouse is an optional dependency: add{:storehouse, "~> 0.1"}for images.An agent adds an image:
add_image, from an address or a file of the app's, with its placeholder for the others while it comes.Kits take files (
Hakushi.Kit.upload/0,uploaded/4,pasted/3), a style panel has a field for a text ("text") and a button ("action"), a tool may open a picker ("pick"), and an edit may move and resize its node in the same change (box;:updatetakes:x,:y,:widthand:height). A text withdata-hakushi-text-ownis written in place where it is.Shapes (
Hakushi.Kits.Shapes): rectangles, ellipses, diamonds, triangles, text and lines, for diagrams, wireframes and whiteboards with no:nodeslot. They are drawn by dragging with their tools (R, O, D, T, and A for a line; Shift for a square or a circle). A shape's text is written in place on a double click, centred and wrapped, and the shape grows with it. A selected shape resizes by its corners and sides, and turns by a knob. A panel beside the selection styles one shape or many in one change: fill, stroke, its width and dash, corners, text size and alignment, and a line's arrows. Each shape has a handle on each side for edges. A line from a shape to a shape is an edge that follows them; a line elsewhere is a node of its own. The colours are the theme's (--hakushi-shape-*) and the app's own. In Markdown, shapes are a Mermaid flowchart, and back.An agent draws shapes:
draw_shapeadds a shape or a line, and changes a shape's text, style and rotation.Kits have a style panel (
Hakushi.Kit.styles/1) and tools that draw a box ("box") or a line from a point to a point ("line")."hakushi:edit"takes several nodes in one change. Two kits' tools with one letter take turns: the letter again takes the next. A node'sdata-hakushi-textis written in place, and itsdata-hakushi-rotateturns by a knob. Every resizable node now resizes by its sides as well as its corners.Hakushi.Test.draw_box/5, andedit/5with a list of nodes.Mermaid:
Hakushi.Mermaid.flowchart/1reads((...))as:circle(was:round) and[/...\]as:trapezoid.People choose the paper:
papers(off unless given) puts a picker in the controls, with the papers people choose from (truefor dots, graph paper, crosses and none, or a list, with papers of the app's own) and the size of their squares. The paper chosen is the graph's (Hakushi.Graph'spaper, kept bydump/1andload/1), and choosing it is a change,:paper(Hakushi.Change.paper/1): it goes throughon_change, ⌘Z takes it back, and on a shared canvas everyone sees it.Hakushi.Test.paper/4chooses one,paper/2reads it.Papers of the app's own (
Hakushi.Paper): a pattern of CSS, named inbackgroundorpapers, drawn with the canvas's properties.An exported image may have the paper:
Hakushi.Live.export/2withpaper: true.Ink (
Hakushi.Kits.Ink): drawing on the paper with a pen, over anything on the canvas. A bar of tools at the top of the canvas has the pointer, a pen and a highlighter (in seven colours and three widths), an eraser of whole strokes and a lasso that selects strokes and nodes alike, to move, copy or delete them (V, P, H, E and L; Escape for the pointer). The line is smoothed as it is drawn, from every point the browser gathers (getCoalescedEvents), thinner and thicker with a stylus's pressure, and sharp at any zoom; when the pen lifts its points are simplified, and the stroke is a node ("ink-stroke", its points relative to its box) that goes through the rules andon_change, moves, goes in the frame it is drawn over, copies, exports and undoes as any. Once a pen is used, a finger pans and only the pen draws. A stroke is "Drawing" to a screen reader, near the node it is nearest.On a shared canvas, the others see a line as it is drawn: it goes in the person's awareness (
ink), as a drag does, and becomes a change when the pen lifts.An agent draws:
draw_on_canvas(Hakushi.Issho.tools/0) draws strokes, or a ringarounda node. The people see its pen draw each stroke, under its pointer, unless it sayslive: false, the app setsconfig :hakushi, Hakushi.Issho, ink: [live: false](ortravel: false), or no one sees the canvas;ink: [speed: ..., max: ...]set how fast it draws, and for how long at most.Kits have tools:
Hakushi.Kit.tools/1(a bar of tools on the canvas, which draw, erase or lasso),Hakushi.Kit.drawn/3(the node of a line drawn with one) andHakushi.Kit.label/2(a node's name for screen readers).Hakushi.Test.draw/5draws as a pen does.
Changed
- A shorter README: what Hakushi is, the install, a first canvas, and links to the guides.
Fixed
- An agent that a person paused or stopped (Issho's
Issho.Work) changes nothing on a canvas, and does not go there: its tools that change something say why at once, as Issho's room tools do. Before, its pointer went and dragged, and only the change was refused. - A shared canvas skips a change it has already, rather than read the whole graph again: on Issho 0.4 a canvas hears its document's changes before it reads the graph, so that none falls between them, and one may come twice. A change of a document that started again (another epoch) reads it again.
- What a person selects on a shared canvas is their place in the room
(Issho's
"place",%{"node" => id}or%{"nodes" => ids}): Issho's comments go on it, and Issho's pointers show it when their pointer is off the canvas, as a text's selection and a grid's cell already are.
0.3.1 - 2026-10-22
Agents read what a person sees of a shared canvas, and a selected node's edges are drawn above the nodes.
Added
- An agent reads what a person sees of a shared canvas, which the graph
does not say:
read_canvasgives each node on their screen itsbox, and an edge that passes under other nodes on its waypasses_under, their ids, withseen_by, whose screen it is (the person ofof, else the one the agent acts for, else anyone with it open). Each browser says it in its person's awareness, once a second at most, when it changes (see the Collaboration guide).
Fixed
- A selected node's edges are drawn above the nodes, in the colour of a selection, and so is a selected edge, with its knobs: an edge that passes under another node on its way is seen going to its own end. Before, the node it passed under hid the rest of it, and it looked as if it joined that node. A click on such an edge over another node is that node's. An exported image has every edge under the nodes, as before.
0.3.0 - 2026-10-22
On Issho 0.3: the canvas is a block of an Issho page, agents move with Issho's motion, and following is the room's.
Changed (breaking)
- Requires
{:issho, "~> 0.3"}. - A shared canvas joins with
issho, the keys of every component (Issho.Join::instance,:document,:can,:people,:follow,:on_behalf_of,:claims):<.canvas issho={[instance: MyApp.Issho, document: id]}>.liveandfolloware removed, and raise with what to write instead; an unknown key raises.people: falsehides the canvas's avatars. - The canvas's own keys of a person's state (
cursor,selected,viewport,dragging) are under its document, in"blocks"; Issho owns the top-level keys. on_change's context hasby, who made the change: on a shared canvas anIssho.Identity(a person, an agent with its real name and whom it acts for, or the server); alone, the canvas'suser.userstays, for now.Hakushi.Issho.change/4takes a change or a list, and an:author(a key or the app's actor,"server"unless given, was"app").- An agent's tools name a canvas by its document, whose room is the
part before the first
"/". - An agent moves with
Issho.Motion: only when someone of the room sees the canvas, within a tool call's time budget (Hakushi'stravelstill turns it off for the canvas's agents, Issho's for every component). - On a page with Issho's pointers (
Issho.Components.pointers/1), they draw everyone's pointer on the canvas too: the canvas says where its points and nodes are on the screen (Issho'slocate) and which point is under a pointer (point), so a pointer goes from the page onto the canvas and back as one, and each person sees it on the same point of the canvas, however theirs is panned. Alone on a page, the canvas draws its people's cursors itself, as before. - The avatars are buttons that follow someone (
data-issho-follow-control). - A canvas's document is what your Issho policy says
(
Hakushi.Issho.document/2in itsdocument/2), whoever opens it first: its page, an embed of it in a document, an agent with no browser. A canvas's owngraph,rulesandon_changemake the document only when the policy does not say (Issho 0.3). The Collaboration and In a page guides show how to share one canvas in several places.
Added
A place on a canvas has a documented format, part of the API (
Hakushi.Issho.Place: a point, a node or nodes), which Issho checks for the canvas's document.Kits: a kind of canvas in a module (
Hakushi.Kit), its nodes, how people make and edit them, its paper and its rules, used in one line (kits={[Hakushi.Kits.Schema]}): the canvas draws, makes and edits their nodes by itself, and an edit is the person's change as any other (checked, undone, shared). Kits are optional, and an app writes its own with the same callbacks (see the Kits guide).Three kits:
Hakushi.Kits.Schema(tables, typed columns, primary and foreign keys, a foreign key only between columns of a type),Hakushi.Kits.Flow(a trigger, steps, conditions with Yes and No) andHakushi.Kits.Notes(sticky notes in five colours, which resize).kit_items/1: the items of kits, to drag onto a canvas or click.A shared canvas in a page of another room (a canvas embedded in a document): its people are its room's, wherever they are, so someone reading the document and someone on the canvas's own page see each other's cursors. Issho's pointers of the page's room leave the canvas to its room; the canvas draws its people except those whom Issho's pointers of its own room draw. Following someone into the canvas from the page follows them on it, and following from the canvas's avatars brings it into view. The same canvas twice on a page: the copy the person uses says where they are.
A shared canvas that goes while its LiveView stays (an embed deleted) releases its room (
Issho.Live.release/2): its person leaves the room.A read-only shared canvas is a viewer of its room (
can: :read).usermay be the app's own struct: the canvas joins as the app'sIssho.Actorsays (its key, name, avatar, colour), the same person as in the page's other blocks. A person with no colour gets Issho's palette colour (--issho-person-N), as in Issho's lists, with Hakushi's as the fallback.A kit's canvas (
canvas/0): its paper, tidy up and rules, which the canvas's own attributes override;background,grid,snapandlayoutdefault to the first kit's.Kits write their canvas as Markdown, a Mermaid diagram people read and Markdown viewers draw (an ER diagram, a flowchart), and read it back, each node where it was (
Hakushi.Kit.to_markdown/2,Hakushi.Kit.from_markdown/2); a diagram a person wrote is read too, and laid out.Hakushi.Mermaidhas the helpers for a kit of an app's.canvas_embed/1: a canvas in a frame, for a page with other things (a Kotoba embed): a title, the kits' items, the app's actions, full screen (see the In a page guide).wheel={:scroll}: the wheel and one finger scroll the page around the canvas; ⌘ or Ctrl and the wheel, or a pinch, zoom.refit: the canvas shows all of its nodes again when its box changes size (full screen and back).Hakushi.Test.edit/5,Hakushi.Icons, the kits' words in six languages,--hakushi-embed-height.The development app's design spec (
/spec): a page of text with a canvas of each kit in it.Following someone, from the canvas's avatars or the page's people list: the canvas shows what they show (their viewport, sent with their cursor as
viewport), or keeps an agent's pointer on the screen. A wheel, a press or a key on the canvas stops following. A canvas that shows its people says whom it follows: "Following Ada", with Stop, in their colour, and a ring of it around the canvas, in Issho's look of following, the same on every block of the page (restyled with Issho's--issho-following-background,--issho-following-textand--issho-following-ring).follow={false}: a shared canvas that takes no part in following, though its room's other blocks do.An agent's pointer travels as a hand does, in a straight line, and the node it drags goes with it: only where it goes is sent (once for a move, once for a drag, rather than a stream of frames), each browser draws the way there, and the agent waits as long as that takes before its change lands.
config :hakushi, Hakushi.Issho, travel: falsefor changes that land at once.Others' pointers and the nodes they drag glide to each new place, longer the farther they go (up to 400ms; none with reduced motion), in the canvas's coordinates, so a pan or a zoom on the way keeps them right; a person's pointer is smoother for it.
Fixed
- A shared canvas that goes with its page (a navigation) throws no error: it let its room go through a LiveView already gone, whose promise no one awaited.
- A canvas's box clips what is outside it, rather than hiding it: a
focused node half out of the canvas no longer scrolls the canvas
(
overflow: clip). - A canvas of many nodes mounts in linear time: the nodes are all measured before any is placed, rather than the page being laid out again for each node (seconds for two thousand nodes in Safari).
- A pan paints nothing again: the paper moves as a layer, by less than a period of its pattern, and the world is a layer while a pan goes on.
- A pan of a large canvas keeps its frames: a node that comes back into view is not news (it culled every node again on the next frame, redrew the minimap and the edges), an edge's path is written only when it changes, a node another person has selected is redrawn without laying out the canvas again, and in WebKit the edges are painted with the world rather than as a layer of their own, which cost more on each frame.
0.2.0 - 2026-10-21
Agents on shared canvases, on Issho 0.2.
Changed
- Shared canvases need
{:issho, "~> 0.2"}.
Added
- Agents on shared canvases:
Hakushi.Issho.tools/0, tools an agent uses as a person uses the canvas, however it reaches the app (its own model through an API with Issho'sIssho.Tools, or MCP withIssho.MCP): read it, point (the people see its pointer, with its name), add nodes, drag them (they see the node go), connect them, change and delete them, through the canvas's rules and itson_change. Hakushi.Issho.document/2: a shared canvas's document for your Issho policy'sdocument/2, so that an agent works on a board no one has open in a browser, through the canvas's rules andon_change.- A shared canvas names its document for Issho's page layer
(
data-issho-doc) and draws its people's cursors itself (data-issho-cursor="own"); its awareness merges into the LiveView's session, whose other components keep their keys.
0.1.0 - 2026-10-21
The first release.
Added
Hakushi.Components.canvas/1: a canvas for LiveView, with nodes drawn by:nodeslots of your HEEx, sent as LiveView streams; edges drawn in SVG (bezier, smooth, step, straight; arrows, dots, labels, animated); paper of dots, lines or crosses; pan and zoom by wheel, trackpad, touch and keys; a minimap and controls.handle/1: where edges start and end, on any element of a node, withtype,acceptsandmax; several handles with one id, of which an edge takes the nearest.- Selection by click and box, dragging with helper lines and snapping, resizing, copy and paste, duplicate, delete, labels, and undo and redo of each person's own changes, checked on the server.
Hakushi.Graph,Hakushi.Node,Hakushi.EdgeandHakushi.Change: the data of a canvas, and changes with their inverses.Hakushi.Rules: loops, duplicates and cycles, the same in the browser and on the server.on_change, the app's check of every change, where it saves it; a refused change taken back in the browser, with its reason.canvas_item/1andnew_node: nodes dragged onto the canvas, or made with a double click; in a group, when dropped on one.- Groups (
groupon a:nodeslot, a node'sparent): nodes that hold others, which move, copy and go with them; a drop on a group puts a node in it, a drag off takes it out, in one:movechange. - Edges whose ends people drag to other handles: the
:reconnectchange, checked by the rules. - A tidy up (⇧L, the controls,
Hakushi.Live.layout/2): a layered layout of the selection or of every node, left to right or top to bottom, as one change. - The canvas as an image: a PNG or an SVG of every node and edge (the
controls,
Hakushi.Live.export/2,exportImage()in JavaScript). mix hakushi.install: the hook inapp.jsand the style sheet inapp.css.- Shared canvases (
live={{MyApp.Issho, topic}}) on Issho: each canvas of a topic is a session of one Issho room, whose document is the graph (Hakushi.Issho), with one process in the cluster that orders, checks and sends the changes;on_changeonce for each change; an Issho policy for who may open, read and change each canvas; people with their avatars, cursors, selections and drags. Hakushi.Live: theon_mounthook of a shared canvas (Issho's), and the app's own changes (Hakushi.Live.change/2,Hakushi.Issho.change/3,Hakushi.Issho.graph/2).- The keyboard: Tab between nodes, Alt and an arrow to the nearest node, the arrows to move, C to connect, Enter into a node; a live region for screen readers.
- Culling of the nodes and edges far from the screen.
Hakushi.Graph.Type, a canvas in one column of the database.Hakushi.Test, forPhoenix.LiveViewTest.Hakushi.Messages, in English, Portuguese, Spanish, French, German and Japanese.hakushi.cssand the Sumi theme,hakushi-sumi.css.