← Blog

Editora 0.9.9: asking before it builds

July 21, 2026 · Adrian De Leon · release

Three things in 0.9.9 are worth explaining rather than just listing: a security prompt that had to be designed carefully to be worth anything, a palette feature that was shipped but barely wired up, and a performance bug that was invisible on every machine except the ones that built the release.

Opening a repository shouldn’t run its code

Cloning a repository and opening it in an editor feels like a read-only act. It mostly is, until you hit build.

Maven and Gradle projects conventionally ship their own wrapper, ./mvnw or ./gradlew, checked into the repository. It’s a convenience: contributors don’t need the right Maven version installed. It’s also a shell script that the repository controls, and Editora prefers it over the one on your PATH, exactly as the convention intends. So the first build in a freshly cloned repository runs code that came with the repository, with your privileges.

Editora now asks first, once per folder, with trust inherited by subfolders so a multi-module repository doesn’t nag. Two details mattered more than the prompt itself.

Declining has to mean no build. The tempting fallback is “fine, use the mvn on your PATH instead”. That would be security theater. A hostile pom.xml or build.gradle executes code through your own Maven too, because build plugins and Gradle scripts run in-process. If you don’t trust the repository enough to run its wrapper, you don’t trust it enough to build it at all, so “no” means no.

The gate has to be exactly the wrapper case. There’s one function that answers “would this run repository-controlled code as argv[0]?”, and the same function decides whether the wrapper is preferred in the first place. A test pins those two in agreement, so a Settings command override can’t silently disarm the prompt. npm, Cargo and Go launch your own toolchain and are never gated; Run and Debug always invoke a PATH interpreter, so they don’t prompt either.

The honest limit: this closes the argv[0] hole, not the broader one. Running mvn on a hostile build file is still executing repository-controlled content. Gating that means trusting a folder before any build at all, which is a much bigger behavior change than this release wanted to make.

A feature that was on, but barely

Editora already dimmed palette commands whose feature was switched off. In principle. In practice only 17 features were wired into the gate, so on a fresh install roughly 19 of about 550 commands were ever affected. Debugging, which is off by default, showed its entire command family as fully available. The feature existed and looked broken.

Two things fixed it. First, the missing features got wired up: debugging, AI, the agent, TODO highlighting, spell check, the CSV grid, structured previews, Markdown lint, EditorConfig. Second, and more useful day to day, commands are now dimmed when they have nothing to act on, not only when their feature is off. Markdown commands outside a Markdown file. CSV commands outside a CSV. Git commands outside a repository. Debugger steps with nothing suspended.

That second half needed a deliberate bias. Dimming a command that would in fact have worked is a worse failure than leaving one lit, so the context rules are conservative: they only cover families where every member genuinely needs the same context, with explicit carve-outs (Clone works fine outside a repository).

The part that turns this from annoying into useful is the tooltip. A dimmed row now says why, and what to do: “Unavailable, this feature is turned off. Run ‘Toggle Debugging Support’ to enable it.” It names a command you can type into the same palette you’re already looking at. And when Simple UI mode is what’s forcing a feature off, the tooltip points at Simple mode rather than the feature’s own toggle, which wouldn’t have helped.

Two invariants keep it from eating itself: the toggle that re-enables a feature is never dimmed, and the “why” is derived from the same logic that decides the dimming, so a command can’t be dimmed for a reason the tooltip can’t state.

Consistent titles are a search feature

Three quarters of command titles already read Family: Action (Edit: Cut, Git: Push). The view.* family was a coin flip: 46 prefixed, 48 bare. That sounds cosmetic. It isn’t, because the palette is a search box. Scanning for view-related commands surfaced half of them and scattered the rest.

All 48 are prefixed now, in all six languages, along with tool.* (now Tool Window: <name>, reusing each window’s already-translated name), the markdown.* formatting commands, and lsp.*. One title was actively harmful: tool.go was the bare word “Go”, which collided with the 33-command Go: navigation family in palette search.

A new test pins the convention: a command family is either fully prefixed or fully bare, across all six catalogs. It can’t drift back into the half-and-half state that caused this.

Eight releases without a class cache

Editora trains an ahead-of-time class cache at build time, worth 300 to 480 ms of cold start. Every tagged release from 0.9.1 through 0.9.8 shipped macOS arm64 without it.

The training run launches the real GUI. When JavaFX 26 made Metal the default pipeline on macOS, the arm64 CI runner’s paravirtual GPU aborted the trainer with an Objective-C exception, a process-level abort() that JavaFX’s own pipeline fallback chain can’t catch. The x64 runner has a different virtual GPU and trained fine every time, so it never looked like a systemic failure.

It never reproduced on real Apple silicon, which has a proper Metal device. That is precisely why device testing missed it for eight releases: the machines that could reproduce it were the CI runners, and the only symptom there was one absent file in a ten-thousand-line build log.

The fix is two-part. Training pins a pipeline any runner can render, while the shipped app still uses Metal (the cache’s value is mostly scene graph, controls and CSS, which are pipeline-independent). And a missing cache now fails loudly, with a per-target annotation, instead of silently costing 28% of startup.

Starting up, and staying put

Opening a file from the file manager used to restore your entire previous session first, then show the file you asked for. With eight tabs that’s a second or two of the wrong file on screen, plus a language server starting for each restored tab. Now the requested file is shown first, servers start per tab as you visit it, and --no-session skips the saved tabs entirely. Measured on an 8-file session opening one file: five child processes down to one, 19 seconds of CPU down to 8, 917 MB down to 696.

Two related jumps are gone as well. A restored file no longer flashes to the top of the document, which turned out to be two separate causes: applying syntax highlighting collapsed the viewport a few frames after the file had already painted at its saved caret, and the virtual flow re-anchored itself during a plain layout pass. Both are corrected in the same frame they happen. Correcting a frame later, the obvious approach, only converts a jump into a bounce, which is what two earlier attempts shipped and reverted.

And in Split view, a progressively rendered preview (typst, Mermaid, image-heavy Markdown) no longer drags the editor around. The preview pane re-anchoring as its content grew was indistinguishable from you scrolling it, worth about 100 lines of drift over two seconds.

Smaller things

Case-insensitive search now matches case pairs that change length, so STRASSE finds Straße and FI finds . That’s literal search in open buffers; regex and the ripgrep-accelerated half of Find in Files keep their own engines’ simpler folding, which is a real limit rather than an oversight.

The HTTP client became the .http file’s preview instead of a separate tool window, so the response sits beside the request in the same three-mode view everything else uses. Console panels finally scroll with your configured keybindings, and scrolling back through a running build stays put instead of being yanked to the tail by the next line of output. Test Results tracks the running test and shows a passing test’s captured output, which was being dropped entirely.

And adding a bookmark moved off the gutter click. The breakpoint strip and the Run arrow are narrow targets inside that same gutter, so a slightly-off click added a stray bookmark instead of setting a breakpoint. It’s C-c m or the editor right-click menu now.

Get it

Download from the releases page. The full changelog is on the What’s New page.