# Release notes Paste the current section into the GitHub release when you cut a tag. --- ## 0.2.3 This carries the same fixes as 0.2.2 and nothing else. 0.2.2 went out as a pre-release for one tester, which means the updater never offered it to anyone — so if you are on 0.2.1, this is the build that actually reaches you, and everything in the 0.2.2 section below is new to you. The short version: typing quickly no longer rearranges your text, the syntax colouring no longer goes flat every time auto-compile fires, a long document no longer stalls while you type, and Reveal in File Manager opens the right folder on Windows. What is new since 0.2.2 is only in the repository, not in the app: Hilbert now has a Dockerfile, contributed by Héctor Flores, for running it in a browser without installing anything. It wraps the hosted mode the binary already had (`hilbert --serve`), so the container signs you in, serves the interface and runs the compiler on one port. See the README. Both of the typing fixes are now covered by a test that drives a real editor at 30 ms a keystroke and checks every character landed where it was typed (`npm run test:typing`). The bug it guards against passed every unit test we had, so the test deliberately uses a document heavy enough to reproduce the conditions it needs. ### Worth knowing, if you missed it Hilbert has been able to write right-to-left since 0.2.0, and people who upgraded straight past it may not know. Each line takes its direction from the first real letter in it, so Hebrew, Arabic, Persian and Urdu lines start at the right edge with their punctuation on the correct side, while an English line in the same file is unaffected. There is a switch in the settings if you would rather force one direction, and the bidi marks are on the Insert menu for the lines the heuristic gets wrong. --- ## 0.2.2 ### Typing quickly rearranged the text Reported on Windows with auto-compile at 0.1 s and again at 1 s: letters swapping places as you write, and the syntax colouring dropping out for a moment at the same time. Typing "for good measure." could leave "for god measure.o" in the file — the o moved to the end of the sentence. The editor and the app keep separate copies of the text, and the app's copy is committed a render after the keystroke that caused it. Type slowly and it catches up before anything notices. Type at speed and it doesn't, and the app was writing its older copy back over the buffer — removing the character just typed, which then reappeared wherever the cursor had got to. The fix in the previous build made this quieter rather than better: it changed a visible whole-document flash into a single character moving silently, which is worse. The buffer is now seeded once and written by nothing else, the way a shared session already worked. Text arriving from disk or from a collaborator still reaches the editor; an echo of the writer's own keystrokes does not. Measured on a 228-page document, typing at 60 ms a key: before, 30 frames with the colouring gone, 495 with the spell-check underlines gone, and the sentence scrambled; after, none, none, and every character in order. ### Reveal in File Manager opened the Documents folder On Windows this went to Documents instead of the file, every time. Two separate causes. Revealing the *workspace* asked Explorer to select the project folder, and selecting a folder opens its parent — which for a project under Documents is Documents. Revealing a *file* broke whenever the path contained a space, because Explorer reads everything after `/select,` itself and does not recognise the quoted form Rust produces for an argument with a space in it. A folder now opens, and a file is highlighted inside the folder that holds it. macOS and Linux follow the same rule, since revealing a folder there had the same off-by-one-level behaviour. ### A long document no longer stalls while you type The preview rebuilt every page's text layer and re-read every page's text for the word count on each compile. On a 228-page document that was about eight and a half seconds of work per keystroke burst, for pages nobody was looking at. Pages now get their text when they come into view, the rest are filled in quietly once the typing stops, and the word count waits for a pause. Typing latency on that document went from 130 ms a keystroke to 28 ms, and the window no longer stops responding at all. ### Syntax colours going flat for a moment on every auto-compile On a long document the editor would blink to plain, unhighlighted text as auto-compile fired — colours gone, spell-check underlines gone, letters appearing to shift — and come back a fraction of a second later. At a half-second compile interval it was happening often enough to be hard to write through. The editor takes its text from the app as a property, and the layer between the two brings them back into step by replacing the whole document in a single edit whenever they disagree. Monaco reads a whole-document replacement as every line being new, so it throws away the tokens for the entire file and every marker in it, then paints plain text until it has worked through the file again. On a short file that is a frame; on a manuscript, with a compile and a PDF re-render already on the same thread, it is long enough to watch. And it landed on the beat of auto-compile because saving is what makes the app's copy of the text and the editor's copy disagree in the first place. Now the two are reconciled before that layer looks, and only across the span that actually differs — the same kind of edit typing a character makes. The shared head and tail of the document are left alone, so their tokens and markers survive. Measured on a 62-page document with a word changed behind the editor's back: before, the file went to a single colour and lost all its underlines for about a second; after, neither changes at all. Registering the Typst language a second time had the same effect on colours, and that happened once for every editor that opened. It now happens once. --- ## 0.2.1 ### "File changed outside Hilbert" when nothing outside Hilbert had touched it Hilbert saves as you type, and it could have two saves of the same file in the air at once — the one compile-on-type started, and the one the next keystroke or a `Ctrl+S` started before the first had answered. The second carried a precondition the first had already made obsolete, and the server answers that the same way it answers a genuine outside edit. So the app announced that the file had changed outside Hilbert, showed you its own two versions to choose between — which is why the two panes looked identical — and offered to put the older one back. It took a slow write to make this common, which is why it was reported from a Google Drive folder: anything that holds a write for a moment, a synced folder, a network share, an antivirus filter driver, leaves the next save alongside the last one every time. On a plain local disk the window is narrow, which is why others saw it only now and then. Saves for one file now go one at a time, and only the newest one waiting is kept: the others were only going to write text it already contains. That also fixed a lag nobody had reported — with writes taking a second each, the file used to trail the editor by fifteen seconds after typing stopped, and now settles in one. The file-watcher no longer inspects a file while a save of our own is on its way to it, since the write reaches the disk before its reply reaches us and in that gap our own text looks like somebody else's. A real outside edit still stops and asks, and the version it offers to keep is now what is in the editor at that moment rather than a snapshot from when the save started — that snapshot could be a few keystrokes behind, so the button meant to protect your work could undo the last thing you typed. ### Shared sessions no longer ask you to resolve your collaborators' text The same conflict prompt could appear during a shared session, where it made even less sense: everyone's edits are already merged in the buffer, so there were never two sides to choose between. A save rejected in a session was retried against the file's new hash, but in a session the file is being written by everybody, so it could change again before the retry landed — and that second rejection reached the person as a prompt. The session's buffer is now written straight out, which is the decision the app had already made. In a two-person test, twelve characters typed at once from each side: before, both people got the prompt and only half the text ever reached disk; now neither does and all of it does. ### A scrollbar no longer lands on top of the document tabs Open enough files that the tabs overflow and moving the pointer over them made a horizontal scrollbar appear across the row, in a row thirty-four pixels tall with nowhere to put one. Which way it got in the way depends on the desktop: with GTK's overlay scrolling on, the default, the bar is painted over the tabs and comes down across the close buttons; with it off it takes twenty-two real pixels and pushes the strip to fifty-six. The bar is gone in both cases. The wheel scrolls the strip instead, and selecting a file that has scrolled out of sight brings it back into view. ### Moving between open files without the mouse `Ctrl+Tab` steps forward through the open files and `Ctrl+Shift+Tab` back; `⌘/Ctrl+PageDown` and `PageUp` do the same, as does `⌘⌥←/→` on macOS. `⌘1` to `⌘8` jump straight to a file by position and `⌘9` is always the last one, wrapping round at either end. None of them fire while a dialog is open. ### Slide Studio keeps up with long decks Moving anything on a slide used to re-render every thumbnail in the rail, so the cost of a drag grew with the size of the deck: at 120 slides a single frame took 14 ms of a 16.7 ms budget, and it showed. Now only the slide being edited re-renders, and a drag costs the same whether the deck has six slides or a hundred and twenty — 6.3 ms at 120 slides, down from 14.1. Undo got two fixes along the way. Dragging a slider used to push one history entry per pixel of travel, so a single gesture flushed everything you had done before it out of the sixty-entry history; one drag is now one entry. And typing into an element on the canvas made no entry at all, so undo skipped straight past whatever you had written. ### Two pictures, one height — on slides, and correctly The Slide Studio's Image tool now shows the actual picture rather than a hatched box with a filename in it, at the aspect ratio the file really has, so the rectangle you drag is the one that ends up on the slide. Pairing two images side by side at matching heights, which the document Image Placer gained in 0.2.0, is now on slides too, with a template to start from. The document version needed fixing while this was being built. It came out right only when both pictures happened to be more than 100pt wide — narrower ones measured at their own size, which threw off the widths and left the heights visibly unequal — and filling in only one of the two captions made the document fail to compile at all. Both are fixed. --- ## 0.2.0 Notebook code now runs inside a real sandbox. You can write right-to-left. Plots can be vectors, not just pixels, and updating no longer looks like it has hung. Proofreading no longer takes the editor down with it, the outline folds, and the diagram builder knows about Wilson lines. ### Code you run is confined by the operating system Until now the only thing between a notebook cell and the rest of your machine was a list of patterns: text mentioning `subprocess` or `socket` was refused, and anything that got past was on its honour. That is a guardrail, not a boundary, and it had the two failure modes guardrails have — it turned away perfectly ordinary code for saying `os.environ`, and it would not have stopped anyone who meant it. Runs now happen inside a box the kernel enforces. On Linux that is bubblewrap: the cell gets its own process, IPC and hostname namespaces, an empty network namespace, a read-only view of the disk with `~/.ssh`, `~/.gnupg`, `~/.aws` and the other credential directories replaced by empty ones, and exactly one place it can write — its own run directory. macOS gets the same two guarantees, writes confined and no network, through Seatbelt. Figures still reach your document: the app copies them out afterwards, the cell never touches `assets/` itself. Because the kernel is holding that line, the pattern list steps aside where a sandbox is active. Code that was refused for mentioning `shutil` or `os.environ` runs now. Python and Julia are confined. Wolfram is not, and that is deliberate: `wolframscript` launches a separate kernel over a loopback socket and works out which kernel to start from state outside the run directory. Confined, it does not start; given just enough to start, it silently runs a different version of Mathematica than the one you get in a terminal. Quietly changing which kernel does someone's algebra is worse than leaving it alone, so Wolfram keeps the pattern list as its guard, exactly as before. Two things change for you. A cell has no network, and a cell cannot write outside its run directory — so a script that downloaded a dataset mid-run, or wrote a CSV next to the document, needs adjusting. `HILBERT_SANDBOX_NET=1` gives the network back, `HILBERT_SANDBOX=off` turns the whole thing off, and App Settings → Interpreters says which of these is in force. Windows has no sandbox available, so there the pattern list still applies and the app says so. ### A hosted server is stricter about who is who If you run Hilbert as a shared server (`--serve`), four things changed. It will not run code it cannot confine. Install `bubblewrap` alongside it; the supplied systemd unit needed loosening in two places for that to work, and `deploy/hilbert.service` now has both, with the reasons written next to them. Signing in no longer hands the browser the server's own API token. It gets a signed session that says when it expires, and the server checks that rather than trusting the browser's word — sessions used to be valid forever once issued. `POST /auth/revoke-sessions` ends every session at once without changing the token everyone signs in with, and `POST /auth/logout` ends just one. A cell could name a figure like `../../../something.png` and have the server move that file into the project. It cannot any more. Wrong passwords now slow the next attempt down, "open this link" no longer asks the *server* to open it, and `HILBERT_PUBLIC_HOST` lets you pin the name the server should be reached by. ### Writing right-to-left Hebrew, Arabic, Persian and Urdu were awkward in two separate places, and each needed its own fix. In the editor, every line was laid out left-to-right whatever you had typed into it, so a Hebrew sentence ran from the wrong margin with its full stop stranded on the wrong side. Each line now takes its direction from the first real letter in it, the way a bidirectional editor should: Hebrew lines start at the right edge, `#set text(…)` and your English paragraphs stay where they were. App Settings has the switch if you would rather force one direction or the other. That rule knows Typst rather than just counting letters. Maths, raw blocks, labels and code are written in Latin whatever language the document is in, so they are skipped when working out which way a line runs — without that, `#emph[שלום]` comes out left-to-right, because the first letter in it is the `e` of `emph`. Where the rule still reads a line differently from the person who wrote it, `Ctrl+Shift+X` turns that line round by hand, cycling through right-to-left, left-to-right and back to deciding for itself. That override lives in the editor and never touches the file — a directional mark before `= Heading` would stop Typst seeing a heading at all. Those Latin islands are also fenced off from the reordering itself, so a formula in a Hebrew sentence stays whole. `$1+1=2$` used to come back as `2$=$1+1`, its two dollar signs dragged into the middle of the sum; the same happened to `@labels`, raw spans and `#calls`. Two of them next to each other keep their own order as well — a reference followed by its label, `@sec:intro `, or two short formulas — which is the case where anything short of the real Unicode isolate gets it wrong. And an English line with one Hebrew word in it was being turned round in its entirety, `The word שלום here` reading as `here שלום The word`, because the editor let a syntax-highlighting token decide the direction of a whole line rather than the other way round. In the PDF, direction comes from the document's language, and nothing in Hilbert ever let you set one — every template said `lang: "en"`, so Arabic came out left-aligned and misordered no matter how it looked while you typed it. Document Settings now has Language and Text direction alongside font and size. It also warns when the font you have chosen has no glyphs for the script you have picked, which is the difference between a PDF that reads properly and one full of boxes. For the lines no heuristic gets right — a price in a Hebrew sentence, an English product name in Arabic — Insert → Text Direction wraps a selection in a directional isolate or drops in a single mark. The marks are real characters that travel with the file, and a hairline in the margin of the text shows where each one sits, so a file that reorders itself is a file you can still fix. The editor used to draw them as a `[U+200F]` box, which replaced the character and so kept it from having any effect at all; what you see now is what the PDF will do. Two more places where a line was read as prose when it was not. Under the forced right-to-left setting, `#set page(paper: "a4")` came out as `set page(paper: "a4")#`. And a Hebrew string inside a fenced code block or a display formula turned that line round, because the rule looked at one line at a time and could not see it was inside a block that started further up. ### Proofreading cannot take the editor with it Switching proofreading on could kill the app outright, losing whatever was unsaved. The cause was one line deep in a dependency: Harper's thesaurus is a compressed blob that asks for a 128 MB decompression window against a 100 MB limit and gives up by panicking, and the release build was configured to treat any panic as fatal. The rule that reaches for that thesaurus suggests livelier synonyms — noise in technical prose, and its output was already being discarded here — so it is off, and the thesaurus is never unpacked. Panics elsewhere in the backend now unwind rather than abort, so a bad line of prose costs you one empty result rather than the editor. The panel also stops claiming your document reads clean before it has read it. An empty list meant two different things — nothing found, or nothing checked yet — and it said "No issues" for both. On a paper of any size the first pass takes a moment, and for that moment you were being told it was clean. ### The file outline folds A heading with anything under it now carries a twisty, the way Overleaf's outline does: shut a section and its subsections go with it, click the title and you still jump there. Titles show what the heading says rather than what the compiler needs — a trailing `