About

Changelog

What Draughtsman 0.1.0 contains, what changed after the first full pass of testing, and the known limitations. This is the CHANGELOG.md that ships in every archive, reworded where it points at files only the project's developers have.

Reading this page

The Unreleased section lists changes made since the 0.1.0 text below was written: the final usability fixes, password recovery and email settings, the upgrade guarantees, the Windows install fixes, and, since 1 October, the AI endpoint policy, git sync, the evaluation notice, browser print as the default PDF, the theme-from-a-website reader, draw.io and Visio import, document thumbnails and the Text pane. 0.1.0 itself has not been cut as a numbered release yet; the section describes what the codebase contains today. Regenerated on 5 October 2026 from main at commit 0b50023.

[Unreleased]

Breaking changes

  • The AI endpoint is restricted by provider (the AI endpoint policy guide). OpenAI, Anthropic and Azure OpenAI may use only their published hosts (api.openai.com, api.anthropic.com, *.openai.azure.com) unless the operator lists others under the new ai.allowedEndpointHosts in draughtsman.yaml; Ollama may use this machine and private addresses (a public Ollama host is listed too). An existing ai.endpoint, or an endpoint saved on the AI page, that these rules refuse leaves AI off after the upgrade, with the reason on the AI page and in the log: list the host and restart. Also: a key is never sent over plain http except to this machine (a proxied Ollama's token now needs https); redirects are no longer followed (a gateway that redirects has to be addressed at its final URL); and changing the endpoint's host forgets the stored key, which has to be entered again. The environment key (ai.apiKeyEnvironmentVariable) is sent only to the host in draughtsman.yaml, or the provider's own when the file names none.

Added (AI endpoint policy)

  • ai.allowedEndpointHosts (in draughtsman.yaml only; a host name, an IP address or *.example.com): the hosts, beyond each provider's own, the AI endpoint may name. Listed in draughtsman.example.yaml.
  • Link-local and cloud-metadata addresses are refused for every provider, even when listed: written as an address (including ::ffff:169.254.169.254, decimal and hex spellings and bracketed hosts) or reached through a host name. A name is resolved when it is connected to, every answer is judged, and the connection is made to an address that was judged, so DNS rebinding between a check and a connect is closed. With an HTTP proxy configured the proxy resolves the name itself and that one case is not closed (the page).
  • Two security events, ai.endpoint_host_changed (with the host before and after, whether the stored key was cleared, and the acting admin) and ai.endpoint_refused. The AI settings response gains key.withheld, saying why a key that exists is not being sent to this endpoint.

Added (diagrams in git)

  • draughtsman diff <before.dsl> <after.dsl> says what changed between two diagrams in sentences that use labels, not ids ("Edge Orders API to Postgres relabelled from read/write to reads"), one line per field, prop key or style key, as Markdown or JSON. --ignore-layout collapses position, size, waypoint and port changes into one line ("12 layout-only changes"). The saved time, author and AI provenance lines are listed apart and never count as a change. Exit 0 no semantic change, 1 changed, 2 a file could not be read or parsed.
  • draughtsman check <file.dsl> prints a file's parse warnings and critique findings and exits 1 on an error-severity finding.
  • Both read only the files they are given: no data folder, settings, database, network or git. The git sync guide has the output, a script that reviews a branch, GitHub Actions and GitLab CI recipes (not yet run on a real runner) and a note on the volatile header lines in .gitattributes.
  • The document diff reports more. diff_documents and GET /api/documents/{id}/diff gain documentChanges (title, type, schema version, id and each theme override), volatileChanges (saved time, author, AI provenance, kept apart) and a per-key props list on each changed element. Additive: every existing member is unchanged. The diff itself moved from the MCP library into the core library.

Changed (PDF export prints from the browser by default)

  • File > Export PDF… now works on every install, with nothing added to the server. It draws the same themed SVG as Export SVG and Export PNG on a page of the chosen size (A4, A3, Letter or Fit, orientation, margin: the drawing is scaled down to sit inside the margins, never up, centred, on one sheet) and opens the browser's print dialog, where you choose Save as PDF. Backgrounds print, the bundled fonts travel inside the drawing, and the page has no margin area for the browser to print a header or footer into. If the browser blocks the print window, it prints from a hidden frame in the same page. See the PDF export guide.
  • The "PDF export not installed…" dead end is gone. The entry is always offered and the dialog says what it does instead of saying PDF failed. When the server's optional Chromium component is working, the same dialog offers its file as the primary Export PDF button with Print in browser… beside it; when it is switched on but failing, an administrator sees a note with the reason and printing still works.
  • Unchanged: GET /api/documents/{id}/pdf, the MCP render_document pdf format, export.chromium.* and the health fields behave exactly as before; Chromium is still optional and is not bundled. No dependency, schema or storage change, and the browser route makes no network request.

Added (themes from a website)

  • Make a draft theme from a site's colours. Admin, Themes has a new From a website section: paste a site's CSS (or a page's HTML) and get a draft theme in the existing editor, unsaved, with the contrast table, a list of how each colour was chosen and a list of what was adjusted to pass the contrast checks. Both light and dark are filled (the missing one is derived), the diagram palette follows the brand colour, and nothing is created until you press Create theme from draft. Fonts are never copied. POST /api/admin/themes/extract (cookie admin only; see the API guide) does the reading; it needs no network, so it works on an air-gapped install.
  • Optional: fetch the page for you, off by default. With themes.fetchFromUrl.enabled: true an administrator can type a web address instead: the server fetches that page and up to 8 stylesheets (https only; no cookies or credentials; 2 MiB and 15 seconds in all; same-host redirects only; loopback, private, link-local and cloud-metadata addresses refused unless the host is in themes.fetchFromUrl.allowedHosts), each fetch recorded as a theme.url_fetch security event with the host and outcome. It is one more entry in the outbound inventory, added to the air-gap tests and to draughtsman config check. No schema or theme-format change.

Added (document thumbnails)

  • The Documents page shows a picture of each diagram. The page opens on a grid of cards, each with a thumbnail, the type, the title, when it was last changed and the usual menu (Rename, Duplicate, Delete); a Grid / List choice beside the search box switches to the table, is remembered in your browser, and the grid has a Sort by menu in place of the table's column headers. Pictures load as a card nears the screen, with a grey placeholder meanwhile, and a card whose picture cannot be had shows the diagram type's symbol, never a broken image. Keyboard order and accessible names are the table row's.
  • GET /api/documents/{id}/thumbnail (see the API guide) serves the picture: an SVG that always fits 320 by 200, drawn by the same renderer as the PDF export and the MCP render_document tool, in the organisation's default diagram theme, so it needs no browser or other component and works on an air-gapped install. It is served as an image under the same locked-down policy as an uploaded SVG; its ETag is the document's version plus the theme and build, so a repeat visit is a 304 with nothing drawn. Finished pictures are kept in a bounded in-memory cache (200 pictures or about 20 MB), drawn at most two at a time (limits.thumbnailConcurrency), and limited to 600 requests per user per minute (limits.thumbnailPerUserPerMinute). A very large diagram is drawn as boxes and lines (over 400 nodes and edges) or a grey placeholder (over 6,000); an empty document shows a dashed frame.
  • No schema, storage or export change. Nothing is stored in the database (a render takes a few milliseconds), and what the PDF export and render_document draw is unchanged.

Added (air-gapped positioning: provable, documented)

  • A software bill of materials in every release. The existing CycloneDX 1.5 generator (the server's NuGet closure, the web app's and sidecar's npm packages, the bundled Node runtime; no new dependency, no network) is now run by the archive build, the release build, the Dockerfile and the image export step. Each release archive holds SBOM.cdx.json, the same file sits beside the archives as draughtsman-<version>.cdx.json (listed in SHA256SUMS), and the image holds /app/SBOM.cdx.json. The bill-of-materials check refuses a hollow one (wrong format, wrong version, no server, web or sidecar packages, or a package the sources declare that is missing); the release build exits non-zero unless every one of the six archives carries it; the Docker build fails on a hollow one. The output was validated against CycloneDX's own 1.5 schema.
  • A test that a default server makes no outbound network connection. NoOutboundNetworkTests listens, from inside the process, to every DNS lookup, TCP connect, HTTP request and TLS handshake the .NET runtime announces while a default instance does first run, sign-in, create, save, layout, text exports, an SVG render, PDF and AI routes, a password-reset request, an API token and an admin export, and fails on anything aimed off the machine. OptInNetworkFeaturesTests shows the four features that can call out (AI endpoint, SMTP, Postgres, the PDF browser's one-time download by an administrator) off and silent by default and heard when switched on, that the set of source files that can open a connection or start a process is closed, and that the licence issuer is not part of the server.
  • The web app and the sidecar are checked for external hosts. The offline-bundle check fails the build if the web bundle loads from, or names, an address outside a short reviewed list (namespaces, schema identifiers, one placeholder), if it uses an API that sends data to a host of the page's choosing, or if the sidecar imports anything from Node but node:crypto and node:readline. It runs in the local verification run, CI, the archive build (on the built bundles and on each archive's copy) and the Dockerfile.
  • The offline container check runs the release image with --network none through the whole default workload, draughtsman backup and draughtsman config check, and fails if its log shows anything tried to leave.
  • The air-gapped install guide: installing and upgrading with no internet (archives, image tarballs, the licence file, the optional PDF browser, backups), a table of what leaves your network and when, and six checks you can run yourself.

Added (draw.io import)

  • Bring in an existing draw.io diagram. POST /api/import/drawio, MCP create_document with format: "drawio" (and an optional page) and the editor's Import draw.io dialog (home screen and File menu) read a .drawio or .xml file, plain or compressed, into the shapes, containers and connectors of one page of it, with their labels, sizes and positions. Positions come with the file, so nothing is laid out. A kind guessed from a shape is flagged for review, and what has no equivalent (stencils, pictures, loose connector ends, hidden layers) is a warning, never a failure. A multi-page file imports one page (the first, unless you pick another) and says so. See the draw.io import guide. No schema change; the import result gains an optional pages member.
  • The file is treated as the untrusted XML it is. A DOCTYPE is refused unread (so no entity bomb and no external entity), nothing in the file is ever fetched, a compressed page is inflated through a stream that stops at 16 MiB, and element count, nesting depth, attributes, pages and shapes are capped, each refusal naming its limit. A label is reduced to its text, so no tag, script or link from a draw.io label reaches a diagram.

Added (Visio import, and the Lucidchart route)

  • Bring in an existing Visio diagram. POST /api/import/visio, MCP create_document with format: "visio" (the .vsdx file base64-encoded in content, with the same optional page as draw.io) and the editor's Import Visio dialog (home screen and File menu, beside Import draw.io) read a .vsdx file into the shapes, groups and connectors of one page of it, with their text, sizes and positions (Visio's inches from the bottom-left with y up, turned into canvas units from the top-left), rotation, and fill and line style where the file wrote a colour. A shape dropped from a master takes its size and outline from the master; a group becomes a container holding its children; a connector becomes an edge between the shapes it is glued to, with its text, line ends and routing. Positions come with the file, so nothing is laid out. A kind guessed from a master's name or an outline is flagged for review, and what has no equivalent (stencil masters, custom outlines, pictures, shape data, themes, layers, flips) is one warning per kind with a count, never a failure; a connector with a loose end is left out and said so. A multi-page file imports one page (the first, unless you pick another) and says so. No schema change; no new dependency (System.IO.Compression and System.Xml).
  • Moving from Lucidchart, Visio and draw.io: the route in from Lucidchart is its own export to Visio (File, Export, Visio (VSDX)) followed by Import Visio, with an honest list of what is lost (styles beyond a basic fill and line, data linking, Lucidchart-specific shapes) and a checklist. Lucidchart's own .lucid format and the older binary .vsd are not read; the page says what to do instead.
  • The file is treated as the untrusted ZIP of XML it is. The ZIP is checked before any part is read (3,000,000 bytes, 5,000 entries, 64 MiB for one part and 256 MiB for all as the directory claims, nothing unpacked to disk): an entry name that could write outside its folder, a repeated name, an encrypted entry, a relationship that climbs out of the package, a size header that lies in either direction (each part is counted as it inflates and must match its declared length and CRC) and a ZIP64 claim of gigabytes each refuse the whole file. A DOCTYPE in any part is refused unread, nothing is fetched, and element count, nesting depth, attributes, shapes, pages, masters and connections are capped, each refusal naming its limit.

Changed (no schema library, no fee to carry)

  • The document schema is checked by our own code. JsonSchema.Net, and the two packages it needs (JsonPointer.Net, Json.More.Net), are published under the Open Source Maintenance Fee EULA: whoever redistributes the compiled library in a product owes the project a monthly fee, which would have travelled with Draughtsman to every customer. DocumentSchema.Validate now runs SchemaValidator, written for exactly the keywords the document schema uses, so no archive or image contains a library under that licence and THIRD-PARTY-NOTICES.txt no longer carries it. The document schema and the MCP get_schema tool are unchanged. No new dependency.
  • Nothing a person could see changed. A differential corpus of 10,446 texts (every example, golden and upgrade-fixture document, every document the repository's own tests validate, 3,324 directed probes and 6,867 mutations) was recorded from the old library and replayed through the new validator: the same verdict and the same location: message lines in the same order for all but 111 cases, each of which the old library answered with an exception (below). The official JSON-Schema-Test-Suite cases for the keywords implemented run as test data too.
  • Faster. Validating the 2,000-node benchmark document takes a few milliseconds instead of tens, and a stream of small documents about a seventh of the time (SchemaValidationPerformanceTests reports the numbers).
  • A schema keyword the validator does not implement now fails a test instead of being ignored: adding oneOf to the document schema means implementing it in SchemaValidator in the same change.

Fixed (a document that made schema validation throw is now a plain 400)

  • A schemaVersion too large to compare (1e30, a 30-digit number) and a string containing a lone surrogate ("\ud800", which JSON allows and UTF-16 text does not) at a member the schema reads as text (an id, a shape, a colour) made validation throw: the document API answered 500. They are now ordinary violations (/schemaVersion: Expected "1", /nodes/0/id: Value is not valid Unicode text). A lone surrogate in the name of a member of props or theme is no longer an error at all.
  • Not changed, and now written down: a document that names the same member twice is validated by its first occurrence while the server keeps the last (a recorded follow-up).

Added (upgrade safety)

  • Upgrades are proven, not promised. The repository now holds the data folder of seven earlier builds (the first pushed commit and six commits that changed the schema or the data folder), each made by running that build and filling it through its own HTTP API with users, tokens, documents of every type (including a 2,000-node diagram and a fly-through path), versions, a bin, branding, organisation themes, AI settings with a stored key and a licence. Every test run starts the current server on a copy of each, on SQLite and on Postgres, and checks that every document is byte-for-byte what the old build served, every user signs in with the same password, every token still works with the same scope and expiry, the key ring and stored AI key open, and no table loses a row. A build script makes one; a release is cut only after its version has one (the release build refuses without it), so every later release is proven to open the data it wrote.
  • A migration cannot silently destroy data. A test scans every migration of both providers and fails on a dropped table or column, a rename, a narrowing or type-changing column, a NOT NULL column with no default, or raw SQL that deletes, truncates or drops, unless a written, justified allow-list entry names the fixture that proves the data survives. The rule is expand, then contract: a release only adds, and what an older release no longer reads is dropped by a LATER release, never the same one.
  • A failed SQLite upgrade is undone. The pre-upgrade copy is now checked before anything changes (same size, opens, passes integrity_check); an upgrade whose copy cannot be written (disk full, no permission) is refused (exit code 5) unless you set storage.allowMigrationWithoutBackup: true. After the migrations the database must pass integrity_check and no table may hold fewer rows than in the copy; if a migration throws or either check fails, the copy is put back byte for byte, the server refuses to start (exit code 4) and the message names the copy. Older copies are pruned only after a good upgrade. Postgres cannot be rolled back by the server: its start-up notice is now a loud, specific block naming the pending migrations and the exact pg_dump and pg_restore commands.
  • draughtsman config check reports, without starting the server or changing anything, what starting this version on a data folder would do: the settings in draughtsman.yaml, the schema against this build (current, N migrations pending, or newer than this build), whether the pre-upgrade copy can be written, the licence file and the key ring. Exit 0 would start, 1 would refuse, 3 the database is newer than this build. Run it with the new binary before every upgrade.
  • An old draughtsman.yaml keeps working. A setting a later release renames is read under its new name and a setting it removes is ignored, each with a warning naming what to do, never a failure; an unknown or mistyped setting is a warning that suggests the nearest real one (server.prot: did you mean server.port?). The warnings are in the server log and in config check.
  • Old documents are held to the same standard: a document from the first scaffold commit and from the first pushed commit must load, validate, print as the same DSL, render and re-save without changing.

Documented (upgrade safety)

  • The upgrade and backup guide is now the whole procedure: the guarantee and what it does not cover, a pre-upgrade checklist, patch updates and major upgrades for each kind of install, going back, the release rule and the versioning and release-notes policy. Two things that were true but unwritten are now stated: Data Protection bound the stored AI key to the install path (fixed below: it no longer does); and an organisation theme's updatedAt is its file's modification time, which a copy of the data folder does not keep.

Fixed (stored secrets no longer depend on where the program is installed)

  • An upgrade, a move or a new folder no longer makes the stored AI API key and the saved SMTP password unreadable. ASP.NET Core's Data Protection bound every secret Draughtsman stores to the folder the program ran from (the framework's default application discriminator is the content root), so unpacking a new version into a new folder, or moving the program, left both undecryptable and an administrator had to enter them again. The application name is now the constant Draughtsman, so a secret depends on the key ring in the data folder alone. Tested on the data folder of every earlier build in the project's upgrade-test fixtures (written under the old binding, at a different path): the current server started from another folder reads the AI key and the SMTP password, re-protects them, and a second move to yet another folder needs no fallback at all; the same on real release archives, the Docker image, a backup taken before the change and restored after it, and for key rings protected by a key-encryption key, the Keychain, a certificate and DPAPI (the application name is not in the ring, which is byte for byte unchanged).
  • Secrets an earlier build wrote are carried across, never dropped. The first start of this version reads a secret an earlier build tied to its install folder through a read-only fallback (the folder it runs from; the folders it records in keys/install-paths.json; keyRing.legacyInstallPaths / DRAUGHTSMAN_LEGACY_INSTALL_PATHS; the folder the data folder sits in; then old logs, the folders beside it and the recommended install locations), copies its settings file byte for byte to backups/secrets-<time>/, replaces only that one value through a temporary file and one rename, reads it back, and puts the copy back if anything differs. A secret nothing can read is reported by name with the reason and what to do on Admin > AI, Admin > Email and in draughtsman config check (new Stored secrets section, read-only), and its file is left byte for byte as it was. No new setting is needed for an upgrade in place or one that leaves the old folder beside the new; naming the old folder is needed only when the program moved at the same time as it was upgraded and nothing records the old folder.
  • A browser tab signed in before the first start of this version holds an antiforgery pair this version cannot read once: the refusal now carries a fresh pair and the editor retries its save with it, so no edit is lost.
  • Compatibility note. Going back to a build older than this one after this version has re-protected a secret needs ai/settings.json and email/settings.json copied back from backups/secrets-<time>/ (the older build reads them only from the folder it ran from, which is what those copies are); otherwise it shows them as needing to be entered again. The upgrade and backup guide ("Where to put the new version", "Going back") and the install guides no longer carry the old "moving the program loses the key" warning. One new setting, keyRing.legacyInstallPaths, and one new variable, DRAUGHTSMAN_LEGACY_INSTALL_PATHS; no new dependency.

Added (email settings in the browser)

  • Admin, Email is now an editor. An administrator can enter the organisation's outgoing mail (SMTP) settings in the browser instead of only in draughtsman.yaml: mail server, port, encryption (STARTTLS, or none only for a server on this machine or one with no user name), user name, password, From address, optional Reply-to (new key email.replyTo) and the public address used in links. Save validates, stores and applies at once with no restart (the mail component reads the current settings on every message), and "Forgot password?" appears on the sign-in page as soon as email can send. Ready-made starting points fill the form without storing anything: Microsoft 365, Google Workspace, Amazon SES, SendGrid, Postmark, Mailgun and "my own mail server", each with the one-line gotcha (SMTP AUTH, app passwords, verified senders, why basic sign-in may be refused).
  • The password is write-only. The page shows "set" or "not set", never the value, and offers Keep (the default), Replace and Clear. It is stored in <data>/email/settings.json as an ASP.NET Core Data Protection payload under the existing key ring, owner-only, and is never returned by any call, logged, put in a security event or written in plain text. The folder is a secret one for backups (left out unless --include-secrets) and for the key ring's migration copy.
  • A clear precedence rule. A value draughtsman.yaml sets (or the environment, for the password: the named variable or password file) wins and locks that field, shown read-only with the reason ("Set in draughtsman.yaml"); everything else is editable. A yaml key left at its default (empty text, port 587, starttls) locks nothing. The API reports the locks and refuses a changed value for a locked field.
  • Two test buttons, both sending one message to the signed-in administrator's own address only: Test saved settings, and Test these values (the form as typed, without storing anything, so a password can be checked before it is saved). They share the existing limit of five per 15 minutes. The result is a short class of failure; the mail server's own reply text is never shown back (a hardening of the existing test message too: it used to quote the server's wording for some failures).
  • Refused on entry: a host that is not a plain host name or address (no web address, user name, path, port, whitespace or non-ASCII), a From or Reply-to that is not exactly one address, a line break or control character in any field (no forged header or injected SMTP command), over-long fields, a bad port, implicit TLS on 465 (not supported by the framework mail client: the page says so and points to the provider's 587 option), and no encryption with a user name for a server on another machine. Email is saved whole or not at all.
  • Only an administrator on a browser session may read or change any of it: an Editor gets 403, an API token gets 403 whatever its owner's role. Saving records the new security event email.settings_changed (what changed by name, never a value) and removing the saved settings email.settings_reset. The page, the API (GET/PUT/DELETE /api/admin/email, POST /api/admin/email/test with optional values), the deployment and admin guides and the example yaml are updated. No new dependency.
  • Said plainly in the UI and the docs: an administrator can make this server connect to any host and port, which is what a mail setting is; firewall outbound connections if that matters to you.

Added (password recovery)

  • A person who has forgotten their password can get back in on their own, two ways that share one mechanism and one page. An administrator creates a reset link (Users, Create reset link; no email needed, so it works in an air-gapped install): the dialog shows the link once, with Copy, its expiry, and a warning that anyone with the link can set the password until it expires. The administrator never sees or chooses the new password. "Forgot password?" on the sign-in page, offered only when outgoing email and server.publicUrl are set up, emails a link; without them the sign-in page says "Ask your administrator for a reset link".
  • The link opens Choose a new password (/reset-password): the 12-character rule and "saving signs you out everywhere else" are shown before the person types, and nothing signs them in afterwards. A used, expired, replaced, tampered-with or deactivated-account link gets one identical answer. Using a link revokes every session of the account and ends its other links; API tokens are left alone (as for a self-service password change).
  • The secret is 32 random bytes stored only as a SHA-256 hash, single use (one atomic conditional update; concurrent uses have exactly one winner), valid for 60 minutes when emailed and 24 hours when made by an administrator (auth.passwordReset.emailedLinkMinutes, adminLinkHours). It travels in the URL fragment, so it is in no access log, proxy log or Referer, and a mail scanner cannot spend it; the page removes it from the address bar at once. It is never logged, never in the security log, and never in a backup or the export.
  • "Forgot password?" answers every address with the same sentence and takes the same time (the mail is made and sent in the background after the answer). Asking never changes anything on the account it names: it never ends a link they hold, never revokes a session, never locks anything; at most three emails go to one address in 15 minutes, and asking is limited to ten per network address and three per address-and-email. Using a link is throttled per network address (failures only) and per account-and-address for dead links. An API token can neither ask for nor use a link; only a signed-in administrator on a browser can create one.
  • Outgoing email (email: in draughtsman.yaml; new server.publicUrl for the address in links): smtp through the framework's client with STARTTLS (refuses to go on if the server will not upgrade or its certificate is not trusted, and never sends a password in the clear to another machine), or file for development. The SMTP password comes from an environment variable or a file, never the yaml. Admin, Email reports the configuration (never the password) and sends a test message to the administrator, reporting the precise failure when it does not work. No new dependency.
  • New security events: password_reset.requested, .link_issued, .email_sent, .email_failed, .link_used, .link_refused (reason class only), .completed, and email.test_sent / .test_failed. A password change by an administrator or the person, a deactivation and the host-shell reset-admin all end a link that was outstanding; reset-admin is otherwise unchanged.
  • Storage: one migration (password_reset_tokens) for SQLite and Postgres. A backup deliberately leaves the table's rows out of its snapshot, so a restore cannot revive a link; ended links are deleted by the daily pass after retention.expiredSessionDays.
  • The Users page no longer asks the server for the user list when an Editor lands on it (the server would have answered 403).

Improved (editor and drawing, from the final usability QA)

  • Dragging a connector onto the body of a shape attaches it to the side facing the other end instead of the side nearest the pointer, so the natural centre-to-centre drag no longer loops round the back. Only a release inside a port's ring pins that port. The same rule applies when an end of a selected connector is dragged to a new shape.
  • Connect selected shapes: press L (or use the command palette and Edit menu) to join the selected shapes in the order they were selected, one undo step, so connectors no longer need a pointer.
  • AI From text into an empty diagram, or one the reply mostly replaces, is laid out as a whole (layered layout); a handful of new connected shapes added to a larger diagram is laid out as a block beside it, not one long row. Decision shapes leave room for a long word, so "successful?" no longer breaks mid-word.
  • A flowchart sample that is clean ("Order approval") replaces the Decision flow sample. Layered layouts (import previews, Tidy, AI) leave every connector orthogonal and let the canvas route it, tables and labelled connectors get wider gaps, and Mermaid import only flags a shape whose kind is genuinely uncertain (a plain box is not), naming the label and the shape.
  • Label editing says how it ends. Icon shapes land at a size and with connection points that sit on the drawn glyph. A participant dragged out of the sequence palette lands between the lifelines it is dropped between. A dropped pool arrives with two lanes at a working size.
  • New diagram offers More diagram types: ER, UML class, BPMN, swimlane and org chart, as starting points made as ordinary flowchart or architecture documents (no new document types).
  • Shortcuts are named for the platform (Cmd and Option on a Mac, Ctrl and Alt elsewhere); Edit attributes, operations and columns appear only where a class or table exists; style swatches are named with their hex in the tooltip and the font size shows the size in use; the Text tab is wider; pasted DSL naming another id keeps the diagram's own id and says so.
  • Export Mermaid saves a .mmd file and lists what it had to simplify (new X-Draughtsman-Export-Notes header on POST /api/export/mermaid); Copy as Mermaid is the clipboard route; Export DSL saves the document's text. AI To document shows each citation as the node's label. The Tour panel labels every travel time Arrive and explains the first. History shows a restore checkpoint's time in local time.

Fixed (behaviour on a busy server)

  • A valid Mermaid or SQL import is no longer refused because the host was busy. The importers' regular-expression match timeout was 500 ms of wall-clock time, which a thread that is not scheduled for that long (a server at a load average of 100 or more) can exceed on a perfectly ordinary line. It is now five seconds, still a bound on a source built to make an expression backtrack (a megabyte of spaces behind a backtracking expression is refused after five seconds, once, because the first timeout ends the import). The same limit now applies to the few expressions in the theme, AI-prompt and PDF code that had none. The 422 for a source that takes too long now says so plainly ("took too long to read; try again, or split the source into smaller parts") instead of claiming the source was not shaped like real input.
  • PDF export is not reported unavailable for half a minute because one launch was slow. A probe or render that only ran out of its time limit (or whose browser did not come up within the launch limit) is remembered for ten seconds, not thirty, and is reported as a timeout with its own advice rather than as "browser not found". The 15 s render limit and the stopping of a hung browser are unchanged.

Added

  • A win-arm64 release archive (the archive build for win-arm64, included in all and the release build), with a checksum-pinned Node 22 for Arm64. Microsoft.Playwright ships no Arm64 Windows driver, so it carries the x64 PDF driver, which Windows 11 on Arm runs under emulation and which printed from the service. The win-x64 archive also runs on Arm, under emulation, two to three times slower to start.

Fixed (Windows, from the install proof on a real Windows 11 machine)

  • The first-run setup code is reachable when the server runs as a Windows service. It was in no log an administrator could find (the Application log source was never registered, and the file log redacts it). It is no longer put in the Application log at all, which every local user can read: the service writes it to setup-code.txt in the data folder, readable only by Administrators and SYSTEM (the permissions are set when the file is created), and logs where it is. New verb draughtsman first-run-code [--DRAUGHTSMAN_DATA <folder>] prints it from an elevated prompt and refuses once an administrator exists. The file goes the moment the first administrator exists, and a restart replaces it with a fresh code. service install registers the Event Log source, so service messages now appear. The first-run page, the Windows install guide, the admin guide and troubleshooting say this.
  • The Windows service's data folder is private. service install made C:\ProgramData\Draughtsman readable and writable-in by every local user (it only added a grant on top of what ProgramData inherits); it now cuts inheritance and leaves full control to SYSTEM, the Administrators and the service account, by SID so a localised Windows works. At every start the server logs a warning, with the fixing icacls command, when Users, Everyone or Authenticated Users can read or change the folder, and a data folder the server creates itself on Windows is made private from its first byte (an existing one is only reported, as on Unix).
  • PDF export through a browser that hangs no longer hangs the request. Under a Windows service Microsoft Edge accepted the request and never answered, leaving the request, a render slot and a driver process behind per attempt, while /api/health said PDF worked. Every render now has a hard limit (export.chromium.timeoutSeconds, 60); an overrun stops the driver and browser that render started, by process id (never by name), and answers 504 with what to do. Health says PDF works only after a page was actually printed, not after a launch. The browser runs on a profile folder of its own under the data folder (chromium/), removed after each render, with no GPU process. The cause of the original Edge hang could not be reproduced afterwards, so it is bounded and documented rather than fixed at its root; Edge and Playwright's Chromium both printed from the running service on Windows 11 Arm.
  • Windows DPAPI that cannot work stops start-up with the message pattern the macOS Keychain uses (what failed, the two safe choices), written to the Application log too, instead of starting, saying "Windows DPAPI" and answering 500 to the first sign-in (an SSH public-key session has no DPAPI master key).
  • service install from a folder under C:\Users, which LocalService cannot read, is refused first with the folder to use instead (it used to create the service and fail to start it with "Access is denied"). service uninstall on a stopped service no longer prints ControlService FAILED 1062. The service shows in Services as "Draughtsman diagram server" (a display name that equals the service name apart from case is ignored by Windows, so "Draughtsman" showed as "draughtsman").
  • The Windows archives no longer carry web.config (an IIS in-process hosting leftover of dotnet publish: an administrator who pointed an IIS site at the archive folder would have got in-process hosting instead of the service); an IIS site proxies to the service. The module's native dll stays inside the executable and unpacks beside SQLite; it is inert.

Fixed

  • PDF export can be turned on from a release archive. The Playwright driver (its Node runtime and package/) was swallowed by the single-file executable and unpacked without its execute bit and without the package folder, so export.chromium.enabled: true never produced a PDF whatever the browser. It now sits whole and executable in .playwright/ beside the executable; the server finds it itself (no PLAYWRIGHT_DRIVER_SEARCH_PATH, no chmod), repairs a lost execute bit where it may, and the archive build refuses an archive without it (and runs the shipped driver once). Proven on the osx-arm64 archive with an empty environment: a PDF from GET /api/documents/{id}/pdf and from the Export PDF dialog, with either a system Chrome path or Playwright's Chromium installed by the shipped driver (./.playwright/node/<platform>/node ./.playwright/package/cli.js install chromium, no Node on the host).
  • A missing or broken driver is reported as the driver, not as a missing browser: GET /api/health (administrators) carries export.pdfProblem (disabled, driver or browser), the Export PDF dialog says "its driver is not usable" and does not offer an install command, and the browser hint no longer says to enable what is already enabled.
  • macOS and Linux archives no longer carry playwright.ps1, and the executable no longer leaves about 137 MB per build under ~/.net/draughtsman/ (it is about 2 MB now: the Node runtime that was inside it is a plain file in the archive). The install guide says where the cache is and how to clear or move it.
  • Help > Documentation opens this server's own copy of the guides (/docs/, behind sign-in, plain text) when about.docsUrl is unset and the archive has a docs folder, instead of the vendor's website, so an air-gapped site has documentation. Set about.docsUrl to an address to keep pointing elsewhere, or to "" to hide the entry.
  • A Mac with no login keychain (a service account, a missing home folder) no longer hangs at start-up. The Keychain call is time-bounded, and the server stops with a message naming the two safe choices (a key-encryption key, or keyRing.protection: none as a deliberate opt-out) instead of carrying on with the key ring unprotected.
  • The admin status page reports PDF export from the same launch probe /api/health uses, so an enabled but unlaunchable Chromium is no longer shown as OK.
  • The Docker image creates /data owner-only (mode 700, the runtime user), so a new named volume no longer triggers the permissions warning, and puts draughtsman on the path for docker exec.
  • Messages and --help point at the upgrade and backup guide, not an older page name.
  • Release archives carry the customer guides (docs folder, SECURITY.md, SKILL.md, CHANGELOG.md), not just INSTALL.md.
  • THIRD-PARTY-NOTICES.txt now includes ajv, ajv-formats, fast-deep-equal, fast-uri and json-schema-traverse, which are declared as devDependencies but bundled into the web app.
  • ai.endpoint in draughtsman.yaml must be an http or https URL with no credentials and no fragment, as the admin AI page already required.
  • On Windows, generated SQL, indented JSON (document export, MCP read_document, the backup and export manifests) and the backup README use \n line endings, the same bytes as on every other host. They had used the platform's \r\n.
  • On Windows, a failed SQL command is no longer written to the Event Log as an error on every clean Postgres start; the Event Log provider's own filter had been overriding the logging.frameworkLevel rule for that category.
  • The optional log file can be renamed or deleted while the server has it open (log rotation tools, clean-up on Windows).
  • An administrator can see and revoke every person's API tokens: Tokens shows "Everyone's tokens" (owner, created, last used, expires, access, status; never a secret) and GET /api/admin/tokens lists them (administrator, browser session only). A revoke is recorded in the security log.
  • Add user and Reset password show the 12-character rule and answer a short password with Password must be at least 12 characters. instead of a silently disabled button; the Reset field has a visible label and the Add user labels no longer wrap away from their inputs.
  • A session ended by a password change, a reset, a deactivation or expiry now ends with a sentence on the sign-in screen (no admin is named), and an open editor keeps its unsaved edits in the browser first. The server says why in a one-word X-Draughtsman-Session-Ended header.
  • A save refused as stale no longer raises "Out of date" for a change to something you did not touch: it is taken in, as the live-sync poll already did, and saved. A real clash still asks. The copy kept by "Reload theirs" is no longer offered on every later open (kept for a week).
  • Dates read 1 Oct 2026, 02:50 in the viewer's time zone (zone and seconds on hover) in Users, Security, Tokens, History, Home, the Bin and the local-copy banner.
  • Narrow windows: no horizontal page scroll from 390 to 1440 px. The top bar is Documents, an Admin menu, Tokens and About (one Menu button under 930 px); the home toolbar wraps so New diagram and the imports stay reachable; the editor's right panel is a sheet opened by a Panel button under 930 px.
  • Every page has a Skip to main content link and exactly one <main> (the editor too); the editor's panel is a region, not a complementary landmark. axe-core finds nothing on any page.
  • Primary buttons are filled with the theme's brand colour (#c0392b, which brandText is held to) instead of accent, so the shipped dark theme's white-on-red buttons reach AA (they were 4.08:1). The contrast checker also holds danger on the raised surface, and a test fails if app.css draws a text pair the checker does not hold.
  • The keyboard focus ring is the theme's text colour, no longer the red used for errors.
  • First-run help is closed until asked, so Create administrator is on screen at 860 px; the Security log names document events in words; an oversized branding image says That file is too large: a branding image can be at most 512 KiB.; an Editor at an admin address sees a heading and a way back (/admin opens Users); the theme editor labels palette colours by role; bin actions and Undo share one status line; accessible names contain their visible text; the AI-off note names AI settings.

Changed

  • MCP create_document refuses dsl or json content whose own type differs from the type argument, warns when the title argument replaces a different title in the content, and takes the POST /api/documents body shape for json (a body id is never kept). patch_document's description lists setFlythrough. The X-Draughtsman-Actor header is documented as not read on /mcp.

Added (the Text pane, beside the canvas)

  • Edit the diagram as text next to it. The editor's Text view (the </> button in the top bar, View > Toggle Text (DSL), or the Text tab) is now a pane you can leave open: whether it is open and which text it shows are remembered in the browser. It shows the DSL to edit and, behind a switch, the diagram as Mermaid to read and copy. The editor guide describes it.
  • Apply is Ctrl/Cmd+Enter, and every message names a line. The text has line numbers; a parse error or warning says Line N, column M and is a link that puts the cursor there; nothing is applied while there is an error. Selecting a node or connector on the canvas marks its line, and clicking a line selects its element.
  • Your unapplied text is never overwritten, and can now be applied after the diagram has changed. When someone else, an agent or the canvas changes the diagram under text you have not applied, the pane says "Diagram changed" and offers Reload text or Keep mine. Keep mine then applies by merging your changes into the diagram as it is now, as one undo step: what you changed lands, what they changed stays, and anything you both changed keeps the diagram's version and is listed, with a button to take yours instead. Before, such text could only be copied out and reset. Closing or reloading the tab with unapplied text asks first.
  • No new dependency, no server change, no change to the DSL grammar or the API guide.

[0.1.0] - unreleased

0.1.0 is the first release. It has not been cut yet; this section lists what the codebase contains today.

Diagram types and shape libraries

  • Four diagram types, each with its own kind vocabulary that get_schema returns: flowchart, architecture, freeform (no vocabulary, every shape family available) and sequence.
  • Sequence diagrams store no positions. The order of the messages is time, and the layout is derived. They support combined fragments with operands and guards, notes, create and destroy, interaction use, activations and a stick-figure actor.
  • Flowchart: the core symbols plus fifteen more from the ISO 5807 set (delay, manual operation, display, stored data, multi-document, merge, extract, on-page connector, summing junction, or, collate, sort, loop limit, internal storage, card). The extra symbols sit in a collapsed palette group.
  • Swimlanes: pool and lane containers, horizontal or vertical. Add, delete, reorder and arrange lanes; resizing a pool repacks its lanes.
  • BPMN: 35 shapes (events, activities, gateways, data artefacts) and the message-flow, association and data-association connectors. The BPMN guide lists what is deliberately left out.
  • UML: class, interface, lollipop and socket shapes with compartments derived from their members, and nine state-machine shapes (initial, final, choice, fork/join, history, entry and exit points, composite state).
  • Database tables: one node per table with aligned PK/FK, type and nullability columns, and a weak-entity variant. A foreign-key connector attaches to a column row, not the table box.
  • Org chart boxes (role, executive, assistant, vacancy) with a dotted-line reporting connector, and a tree layout option.
  • 41 unbranded technology icons (servers, network topology, cloud infrastructure, load balancer, API gateway, cache, CDN, firewall and others). Boundary icons (VPC, subnet, availability zone) draw as labelled containers.
  • Connector ends with crow's-foot and UML notation; straight, orthogonal and curved connectors.
  • Image nodes backed by uploaded pictures, stored once per unique file.
  • Fly-through: an optional camera path saved in the document. Capture keyframes from the canvas or generate them from containers, caption and time them, and play them in Present mode. See the fly-through guide.

Editor

  • Document home screen with create, rename, duplicate, search, sort, a type filter and delete. New diagram offers all four types. Opt-in sample diagrams and empty states.
  • A bin: a deleted document keeps its versions and can be restored for 30 days (retention.binDays), then a daily pass removes it and records that in the security log. Permanent delete is a separate, confirmed step for an administrator or the document's creator, from a browser session only.
  • History panel: every stored version with time, author and size; name a version; compare a version with the saved diagram (added, removed and changed elements, marked on the canvas); restore a version, which stores a checkpoint first so the restore can itself be undone. A Review panel shows the structural critique of the diagram on the canvas.
  • Canvas: pan and zoom, marquee selection, snapping and smart guides, move, resize and rotate, nudge, group and ungroup, drag into or out of containers, fit to contents, align, distribute, z-order, copy, cut, paste and duplicate. Undo and redo are exact for every edit.
  • Connectors: draw from shape handles, re-attach an end, drag a segment or waypoint, reset a route. Orthogonal routes avoid other shapes. Edge labels sit on an opaque plate and avoid each other and neighbouring shapes.
  • Labels edited in place. A styling panel for the selection. Shape palette with search, click or drag to place. Properties panel for connector kind.
  • Command palette (Cmd/Ctrl+K) and a top-bar menu over one action list, so every canvas command has a visible menu entry. Help menu with a keyboard shortcut sheet. Keyboard navigation between regions (F6).
  • Two-way DSL text panel; changes apply as one undo step. Tidy layout (layered, stress or tree) as one undo step.
  • Autosave two seconds after the last change, with a saved/saving/unsaved indicator and retry with backoff. A save refused as stale offers reload theirs, keep mine (confirmed) or save mine as a new document.
  • Leaving the editor with unsaved edits, by any route including back and forward, asks to save, discard or cancel. Escape cancels a move, resize, rotate or sequence reorder in progress.
  • Edits an agent makes to an open document appear live and merge with unsaved local edits.
  • Sequence diagrams are edited by message order: insert by dragging a connector, reorder, and commands for fragments, notes and create/destroy.
  • Theme menu for diagrams. The organisation's default theme is the starting point.
  • Pen and touch support for connecting and dragging. Holds 60 fps over a 2,000-node benchmark document in Chrome.

Import and export

  • DSL: a lossless text form with parser and printer, import and export endpoints, and a /try page. A DSL import keeps the source's document id by default; ?newId=true gives a fresh one, and the 409 for a repeated id says so.
  • Mermaid import: flowchart, graph, sequenceDiagram (fragments, notes, create/destroy) and erDiagram. Constructs it cannot represent warn and flatten.
  • Mermaid export: sequenceDiagram, flowchart and erDiagram text, via the API, MCP read_document and File > Export Mermaid. Shapes with no Mermaid spelling degrade with a %% comment, and the dialog says when a copy is lossy.
  • SQL import for T-SQL, PostgreSQL, MySQL and SQLite DDL: a tolerant reader that turns tables into entities and foreign keys into connectors, with relationships declared in comments drawn dashed. Tables can be coloured by a [TAG] comment. SQL export writes entity diagrams back as DDL.
  • PNG and SVG export in the File menu, client-side, drawn with the editor's own connector routes.
  • PDF export through an optional headless Chromium component (see Known limitations). An Export PDF dialog offers fit-to-drawing, A4, A3 or Letter, orientation and margin. The editor says what is missing when PDF is unavailable, and offers the entry only after a proven browser launch.
  • Fly-through export: one self-contained HTML file that plays anywhere.
  • Whole-instance export (administrators, browser session only): every document as JSON and DSL and every uploaded image, in one zip. Documents in the bin are left out unless ?includeBin=true asks for them.

Agents and AI

  • Eight MCP tools: list_documents, read_document, create_document, patch_document, render_document, diff_documents, critique_document and get_schema. Over streamable HTTP at /mcp and over stdio with draughtsman mcp --stdio.
  • patch_document takes addressable operations (add, remove, update, set prop, restyle, reorder messages, set fly-through) and refuses a write when the document has moved on. Argument errors name the missing or invalid argument.
  • critique_document reports structural defects deterministically (isolated nodes, non-container parents, keyless tables, unbalanced activations and others).
  • render_document returns SVG, or PDF when the Chromium component is present, drawn with the organisation's default theme and the editor's connector routes.
  • Bring your own AI: Ollama, any OpenAI-compatible endpoint, Anthropic and Azure OpenAI. No provider configured means AI is off and its panel is hidden.
  • Generate from text: write a new diagram from a description, or restructure an existing one. An update keeps the geometry, style and props of every element the reply does not touch.
  • Diagram to document: design-doc, runbook or explanation Markdown whose citations link to nodes on the canvas. Only the outline (ids, kinds, labels, edge endpoints) is sent to the model, never geometry, style or images.
  • Agent writes are checkpointed, and the provenance records the provider and model.

Accounts and security

  • Local sign-in with Argon2id passwords (12 characters minimum), server-side revocable cookie sessions and CSRF protection. Every route requires sign-in unless listed as anonymous, including /mcp.
  • First run creates the administrator and needs a one-time setup code printed in the server log; first-run then closes for good.
  • Two roles, Administrator and Editor. An administrator cannot demote or deactivate the last active administrator. Deactivating a user or resetting their password revokes their sessions and API tokens at once.
  • API tokens: per user, shown once, expire (90 days by default, capped by auth.tokens.maxLifetimeDays) and can be read-only. Account and token management and instance settings need a browser session; a token cannot reach them even when an administrator owns it.
  • Failed sign-ins are throttled per account-and-IP and per IP. Sign-in failures look the same whether or not the account exists.
  • Security event log at /admin/security: sign-ins, throttle trips, account, role, password and token changes, recoveries and exports, with actor, target and source IP, never a secret.
  • Self-service password change on the Account page; it signs out other sessions.
  • draughtsman reset-admin <email> recovers a lost administrator password on the host and is recorded in the security log.
  • The Data Protection key ring that encrypts the stored AI key is itself protected at rest (Windows DPAPI, the macOS Keychain, a key-encryption key from an environment variable or a file, or a PKCS#12 certificate). An existing plain-text ring is migrated after a verified backup.
  • Hardening: Content-Security-Policy, frame-ancestors, nosniff, Referrer-Policy and no-store on API responses; __Host- session cookie over HTTPS; per-route request body limits; per-user rate limits and instance-wide caps on PDF, layout, import, AI and MCP (429 with Retry-After); time-bounded importers; server.allowedHosts Host allow-list (loopback names only by default on a loopback install); server.knownProxies accepting CIDR ranges; server.forceHttps; owner-only data folder permissions; a stricter SVG sanitiser for uploads; a sandboxed, script-free, network-free PDF Chromium; scrubbed sidecar environment.
  • Anonymous /api/health reports status only. Capabilities need a sign-in, and detail needs an administrator.
  • Audit actor is always the authenticated user. An agent:<name>/<model> header or argument only annotates it.

Administration

  • Users page: add, change role, reset a password, deactivate.
  • Branding: product name, logo, light-on-dark logo and favicon, applied without a restart. Uploaded SVGs go through a strict allowlist.
  • Organisation themes (/admin/themes): chrome and diagram halves, a default theme for everyone, import and export as YAML, live previews and contrast warnings.
  • Licence page (/admin/licence): state, licensee, tier and update entitlement.
  • AI settings (/admin/ai): provider, endpoint, model and a write-only, encrypted key, with Test connection. The environment-variable key wins over the stored one.
  • About (everyone) and an administrator status page: storage, sidecar, PDF component, AI, licence state, with the exact log command for the host.
  • Help menu, first-run help for finding the setup code per host, and bundled Inter and JetBrains Mono fonts.

Operations and install

  • Release archives for osx-arm64, osx-x64, linux-x64, linux-arm64 and win-x64: self-contained, with the web app, the sidecar and a checksum-pinned Node 22, so nothing else is installed. SHA256SUMS accompanies them. See the install guide.
  • draughtsman verbs: serve, mcp --stdio, service install|uninstall|status (systemd, LaunchDaemon, Windows service), backup, restore, reset-admin, --version, --help.
  • Backup: a consistent snapshot of a running SQLite instance as a checksummed zip; restore verifies first. On Postgres the command prints the pg_dump line instead. Backups include the bin. See the upgrade and backup guide.
  • A SQLite database is copied to data.db.pre-migration-<stamp> before migrating (newest three kept). An older build refuses a newer database and exits with code 3, changing nothing.
  • Retention: per-document checkpoints (50), expired sessions (7 days), documents in the bin (30 days), an asset quota (5 GiB), pruned daily.
  • Logging: framework noise is off by default, levels are set in draughtsman.yaml, and an optional daily rolling file under <data>/logs/ keeps 14 days.
  • SQLite and Postgres storage with the same contract tests. Migrations apply at start.
  • Docker image: multi-stage, non-root (uid 10001), data volume at /data, an optional Chromium build argument, and the image export step to carry an image to an offline host. Release archives need no internet at run time.
  • THIRD-PARTY-NOTICES.txt, generated and served at /THIRD-PARTY-NOTICES.txt, ships in every archive and image. draughtsman.example.yaml lists every setting.
  • Documentation for the people who run it: install guides for Docker, Linux, macOS and Windows, TLS and reverse-proxy deployment (Caddy, nginx, IIS), an administrator's guide, backup, upgrade and downgrade, troubleshooting, a data-flow statement and SECURITY.md. The bill-of-materials generator writes a CycloneDX software bill of materials with no extra dependency.

Licensing

  • Perpetual, per-organisation licence as a signed file verified offline: Ed25519, no licence server, no phone-home, no seat counting. Team (up to 50 people) and unlimited tiers, 12 months of updates.
  • Entitlement is by build date, never the clock. A build made after updatesUntil keeps running and shows an administrator banner.
  • An unlicensed instance is a fully working evaluation. Reading, saving and exporting never depend on licence state.
  • A licence tool and signing library for the maintainer, and a separate issuer service that signs and emails a licence after a Stripe purchase. Neither ships in customer archives.

Fixes of note found in internal QA

  • Connectors start on a diamond's drawn outline, not its bounding box, in the editor, exports and server render.
  • Connectors leaving the same port fan out instead of drawing on top of each other, and routes keep clear of neighbouring shapes.
  • Text on a filled shape is readable against the fill unless the document names a text colour.
  • Opening or closing the shape panel no longer shifts the drawing on screen.
  • Chrome control outlines and the neutral diagram theme's strokes meet 3:1 contrast.
  • Mermaid import maps a flowchart cylinder to a data store, and no longer reports kind guesses as skipped.
  • Claude Code connecting to /mcp without a token now receives a Bearer challenge and a usable hint, instead of a JSON or OAuth error.
  • Clicking Save during a title edit no longer reports a false conflict.
  • A resized shape no longer leaves a connector waypoint inside it.
  • Browser back and forward no longer lose unsaved edits, and importing asks before it creates a document.
  • Edge labels no longer land on a neighbouring shape or on the border of their own table.
  • The exported legend matches the editor, and an unselected sequence participant no longer draws in the selection colour.

Known limitations

  • Sign-in is local only. There is no OIDC or SAML, no password reset by email (recovery is draughtsman reset-admin on the host) and no workspaces beyond default.
  • Roles are Administrator and Editor. There is no Viewer role and no per-document access control: every signed-in user can read and edit every document.
  • No real-time multi-user editing. The open editor polls for newer versions (agent edits included) and merges them, and a stale save is refused.
  • No production licence verification key is committed. A build from this repository cannot verify a licence and runs as an unlicensed evaluation until the production public key is embedded. The release build refuses to build without it.
  • Release archives are not code-signed or notarised. macOS Gatekeeper and Windows SmartScreen stop the first run of a downloaded copy.
  • No container image is published. Build it from the Dockerfile.
  • Windows is a release target, and CI has a Windows leg, but the Windows archive and service install have not been run on a Windows host by the maintainer. A Windows VM for that is planned.
  • PDF export needs the optional Chromium component, which is not installed by default, and render_document cannot return PNG. The server render uses the organisation's default theme, not a theme an individual viewer picked in the editor. It draws a BPMN message flow as a plain arrow.
  • The AI endpoint policy restricts keyed providers to their published hosts (or hosts the operator lists), but Ollama may still reach private addresses, so Test connection can probe them; see the AI endpoint policy guide.
  • Security events are kept indefinitely; there is no pruning job.
  • Postgres is not backed up by Draughtsman (pg_dump is the method). SQL Server is not supported.
  • Fly-through has no video or GIF export, speaker notes, multiple paths or server-side render of a tour.
  • BPMN omits compensation, loop and multi-instance markers, several event types and choreography diagrams.