Skip to content

v0.46.0

Choose a tag to compare

@inkeep-internal-ci inkeep-internal-ci released this 03 Aug 14:03

Minor Changes

  • Comments are the queue for your agent. Highlight a passage, say what's wrong, keep reading — the comment is filed against the exact words you picked, and nothing is sent until you decide the pass is done.

    Before After
    Five things to fix meant five trips to a chat window Comment on each, send once
    Retype the passage so the agent can find it again The comment carries the words it points at
    Lose your place in the doc every time Never leave it

    The queue spans files. Comment on three passages here, open another file, comment on two more, then send. One agent turn, edits landing in both documents. Uncheck anything you changed your mind about before it goes.

    Send now or send later. Writing a comment offers both: Add Comment files it for the batch, Send to AI hands it over immediately. Either way the comment box takes @ mentions of files, the same as the Ask AI composer, and mentions reach the agent as real paths.

    Sends find the conversation you're already in. With a chat open, the queue lands in it rather than starting a detached one; with none open, it starts one. The send button says which before you click it.

    Comments are stored per-machine next to your content and are not committed — a comment here is a note to your own agent, not a message to a teammate.

  • Skills become a first-class surface: find them, bring them in, and own them as versioned content.

    Find a skill. Skills is now a destination rather than a settings tab. Its home leads with describing the skill you want, with Upload a skill and New from scratch as quiet actions beneath. The six most-installed skills on skills.sh render inline with a Browse all link, so installing something that already exists no longer starts by opening a modal.

    Adding a skill opens one dialog with two tabs:

    • Explore browses skills.sh — a most-installed grid on an empty query, keyword search otherwise — and imports a repository-backed or website-backed result in one click. Website publishers install through their .well-known skill index, including every declared file. When skills.sh is unreachable the surface degrades to a GitHub-topic search and the last-good grid, so it is never blank on a cold network.
    • Upload takes a .zip/.skill bundle, a folder, or a remote source: a GitHub owner/repo, a git URL, a skills.sh skill-page URL, or a local path.

    Skills you already have are left alone. Skills OK finds in your other tools (Claude plugins, plus the Claude, Codex, Cursor, OpenCode, Copilot, Pi, and Agents skill dirs) are never imported. They appear as read-only Detected rows, expand to their file tree (loaded lazily, so the sidebar never fetches a preview for every detected skill up front), and are editable in place — changes save straight to that editor's own copy.

    One skill, one folder, many places. Every path lands the skill in the project's skill home (.agents/skills/<name>/ when you have one) as versioned content: history, restore, search, and attribution, with provenance in .ok/skills-lock.json. An identical re-import is a no-op; a name collision lands under <name>-imported rather than overwriting. A new location is a symlink to the source by default, or follows the form the skill's other locations already use; convert any single location when you want an independent copy. Imported scripts are stored as content and never executed.

    Installing a set, not a file at a time. When a preview belongs to a plugin or a website index, it discloses the siblings and opens a picker — each with its description, a select-all, and the level to install at — rather than a one-click "install all 41". The selection imports through a single server-side clone, so ten skills from one repo no longer re-clone it ten times, and one oversized bundle or misspelled name reports itself instead of failing the rest. Website installs also fetch bundle files concurrently and read the origin index once per selection: two skills from open.feishu.cn went from 39s to 9s. A plugin's hooks, commands, and MCP servers are named, never installed and never run.

    The workflow MCP tool is retired. Its five procedures move into skill guidance, so they load on description match instead of costing tool-list tokens every turn. import takes the freed slot, keeping the surface at 21 tools.

    • ingest and brownfield onboarding move into the platform open-knowledge skill, which ships with every ok init.
    • research and consolidate ship with the knowledge-base pack; wiki generate/refresh with codebase-wiki. The platform skill now says plainly when a procedure needs its pack.

    Starter packs ship focused skills. A pack directory may hold a root SKILL.md plus one subdirectory per member skill. software-lifecycle gains frame-a-proposal, write-a-spec, record-a-decision, write-a-postmortem, and review-a-design; knowledge-base promotes research and consolidate to member skills. Pack skills also gained task-based triggers, so software-lifecycle fires on "record an architecture decision" rather than only on a folder-layout match. ok seed --pack <id> installs every skill a pack ships, and no longer reports "already seeded, nothing to do" when the folders exist but a skill is missing.

    Fixes

    • Installing a skill into the .agents hub no longer fails with "Placement path must be a project-relative directory outside .ok/." Asking for a location a skill already occupies is a satisfied request that changes nothing.
    • Skill preview tabs no longer duplicate. A preview's identity includes its source path, and that path moves (a plugin-cache path carries the version; a detected skill relocates when its installed copy is deleted), which minted a second tab with the same label — one of which looked impossible to close. Reopening reuses the tab already open for that skill and level, and a visible tab is always closable.
    • Settings → Skills is slimmed to the install-targets picker; authoring and browsing live in the sidebar.
    • Install counts read 2.7M rather than 2733k, in the active locale's units and separators.

    Under the hood: a read-only cross-harness enumerator (ok skills installed, GET /api/skills/installed), the skills.sh discovery and leaderboard proxies with defensive row-level parsing, the acquire/import pipeline, and the shared skill and Pack model — all exported from the new @inkeep/open-knowledge-core/skills-catalog subpath. Remote imports require explicit opt-in before upstream changes auto-apply; local filesystem sources keep automatic refresh. Rename and scope-move preserve provenance, update eligibility, and copy projections. The canonical-component text in tool descriptions is split so write and edit still list every component id while the authoring reference rides write alone, trimming roughly 940 bytes of always-on context per turn.

  • Editing the globs that scope a frontmatter schema to a set of docs got three fixes, all from the same root: once you pressed Enter, the pattern became something you could neither read accurately nor change.

    Patterns read as you typed them. A committed pattern was displayed in capitals — blog/** rendered as BLOG/**. The pattern on disk was always correct, so nothing was ever scoped wrong by this, but globs are case-sensitive and the display gave you no way to confirm what you had actually saved. The same fix reaches every place these value pills are used, including the schema editor's "Allowed values", where an enum of Draft / Published was shown as DRAFT / PUBLISHED.

    Double-click a pattern to edit it. Correcting blog to blog/** meant deleting the entry and retyping it from scratch. Double-clicking a pattern now lifts it back into the input with the text selected; the corrected pattern is written back in its original position, so an exclude that only applies to the includes before it stays where you put it. Enter or Space does the same thing from the keyboard, and Escape cancels.

    Problems point at the glob that caused them. A pattern matching zero docs was reported in a "Configuration problems" list that named the pattern but not which schema's input it came from — with several schemas and several globs each, you could see something was wrong without being able to tell what to fix. The offending pattern is now marked on the input itself, with the reason on hover, and that is its only home. Deleting a flagged pattern no longer flashes a warning about it on the way out: the problems channel is composed from the file on disk and briefly lags the edit, so a pattern you just removed still had a live finding for a moment. Problems about the schema file rather than a glob still appear in the list.

  • The terminal and agent conversations are now two separate panels instead of one shared dock. The terminal owns the bottom of the editor and still opens with ⌘J; agent conversations own the right panel and open with ⌘L. Both can be open at once, each keeps its own size, tabs, and reload state, and closing one leaves the other alone — so you no longer have to give up your terminal to see an agent. The dock-position toggle is gone with the split; a terminal that used to sit in the right panel moves to the bottom on first launch, and the right panel keeps the width you had set.

    A new tab in the terminal panel opens a plain shell by default — you no longer get dropped into a CLI you never chose. The New button's dropdown still lists every terminal CLI, though, so you can start a tab directly in one; that choice sticks, and the + button, ⌘J, and ⇧⌘J repeat it until you pick Terminal again. The agents panel's New button remains dedicated to in-app agents. Sending a passage to AI — from the selection bubble, a code block's Ask action, or ⌘J with selected text — still uses your preferred AI and reveals the panel where it runs.

    The agents panel now keeps a small tab on the right edge whenever it is closed, so a conversation is always one click away even if you have never opened the panel. The terminal no longer has an edge tab of its own — ⌘J and the View menu open it — which clears the bottom-right corner it used to share with the Ask AI composer.

    The bottom Ask AI composer moves from ⌘L to ⇧⌘L (Ctrl+Shift+L on Windows and Linux), and now hides whenever either panel is open — both are already places to type a request, so it stepped aside rather than offering a third.

  • Code and plain-text files (.ts, .json, .css, .py, .yaml, .txt, and friends) now open as real editors instead of read-only previews — line numbers, syntax highlighting, and live collaborative editing, with every keystroke saved straight back to the file on disk. HTML, SVG, and lockfiles open the same way, straight from a click in the file tree. Markdown keeps its rich editor and Mermaid files keep their diagram editor.

  • A config key that was removed in a newer version no longer discards the rest of the file it appears in.

    Config files accumulate keys that later releases retire. Until now, a single retired key anywhere in a file — your committed .ok/config.yml, your user-global ~/.ok/global.yml, or the per-machine .ok/local/config.yml — invalidated the whole file: every unrelated setting in it silently reverted to its default. A long-dead cosmetic setting left behind in a config could therefore switch off something you actively depend on, with nothing reporting a failure. In one case a retired sidebar preference sitting in a project-local config threw away an explicit autoSync.mode: full, so Git sync stopped running and stayed off.

    Now every config layer strips only the retired keys it finds, keeps every other setting at the value you wrote on disk, and reports each stripped key together with what replaced it. Genuinely broken files are unaffected by this change: invalid YAML and settings that fail validation are still rejected exactly as before. Only the handling of retired keys changed.

    • ok start no longer fails to start over a retired key in your committed .ok/config.yml. It strips the dead key, prints what it found and the replacement guidance, and starts. Previously one stale key could stop the tool from starting at all.
    • ok config migrate can now reach your per-machine config. It defaults to every layer, so the bare ok config migrate each error message tells you to run now fixes the key wherever it lives. A new --scope project-local targets .ok/local/config.yml alone, --scope all is the explicit form of the new default, --scope both keeps its old project-plus-user reach, and --dry-run still previews without writing. Migration only ever removes a retired key and reports its replacement — it never rewrites your other files on your behalf (for example, it will not create or edit .okignore for a removed content.include, because that rewrite is not a faithful one-to-one translation).
    • ok config validate now reports all three config layers. It previously described only your user-global and committed-project files, so a retired key in the per-machine .ok/local/config.yml — the layer the reported bug lived in — got an unqualified . It now reads that layer too, so a stale key is findable without a running server. Reporting is read-only: an unreadable layer is described, never moved aside.
    • New endpoint GET /api/config/diagnostics lists the retired keys currently present across your user, committed-project, and project-local configs, each identified by its scope, file, key path, and replacement guidance. It reads the files fresh on every request, so fixing a file is reflected on the next request without a restart, and it never returns your actual config values.

    Settings that a retired key was suppressing now take effect. Some features deliberately fall back to a safe value when a config file cannot be trusted, and until now a retired key made a file untrusted. Those features therefore ignored what you had written in the same file. Now that a retired key leaves the file readable, your settings in it apply — which for an affected config is a real change in behavior, in both directions:

    • External link previews. A project-local config carrying a retired key had link previews forced off, even when you had explicitly enabled them. Your explicit setting is now honored. If that file has no linkPreviews setting at all, the default (previews on) now applies, and hovering an external link sends its URL to the destination site where previously it did not.
    • Semantic search. A search.semantic block sitting alongside a retired key was discarded, leaving semantic search off. Your configured provider and model now apply, and enabling semantic search sends content to that provider.
    • Editor bridge safeguards. An explicit bridge.* opt-out alongside a retired key was overridden back on. Your opt-out now takes effect.

    If you rely on any of these being off, check the config file the diagnostics endpoint or ok config validate reports a retired key in. A file that is genuinely unreadable (invalid YAML, or settings that fail validation) still falls back to the safe value exactly as before.

Patch Changes

  • Fix a family of frontmatter-boundary bugs that could silently remove body content from a document's view while leaving the bytes on disk intact.

    A document whose body opens with a --- rule pair and carries no frontmatter was re-read as if the span between the rules were YAML, so that content stopped reaching the editor. Compositions that derive one of their two sides now keep the boundary unambiguous — a freshly serialized body re-spells its leading rule, and the property panel keeps an explicit empty block when removing the last property would hand the body's own bytes to the frontmatter parser. Documents that carry real frontmatter are unaffected, and content the agent writes still lands byte-for-byte.

    Also fixed at the same seam: a frontmatter block with no trailing newline was destroyed when a body was appended to it (reachable from ordinary typing and from an agent append), and agent append / prepend silently dropped a leading fenced span of the payload — eating body content when that span was not valid frontmatter. Such a payload is now refused with the same 400 the identical payload already returns for replace; payloads carrying well-formed frontmatter are unchanged.

  • Long values in the frontmatter properties panel no longer spill out of their pill background. A list property holding a long URL used to wrap across several lines while the chip's fully-rounded background ballooned into an oval that the text escaped, with the remove button floating over the text. Wrapped chips now render as a contained rounded rectangle; single-line chips keep their pill shape.

  • The agent composer now shows one action button instead of Stop and Send at the same time. While the agent is working the button is Stop; as soon as you start typing it becomes Send, and your message queues behind the current turn the way it always has. This matches how other agent chats behave and keeps a single clear action in the corner of the composer. Because Stop is hidden while you have a draft, pressing Escape in the composer now stops the agent, and your draft is kept so you can still queue it. Hovering Stop shows the shortcut. A stop that hasn't taken effect yet keeps showing its spinner even if you keep typing, so you can always tell whether the agent heard you.

  • Stop background sync from popping a GitHub sign-in window on Windows.

    On Windows, a project with a GitHub remote could open a "GitHub — Select an account" window every few minutes, unprompted, for as long as the app was running — even while syncing was healthy and pushing successfully. The window came from Git Credential Manager, the credential helper Git for Windows installs by default, not from OpenKnowledge's own sign-in flow. OpenKnowledge already told git not to prompt, but that setting only silences git's own terminal prompt and does nothing about a credential helper's graphical prompt, so every background fetch that missed the credential store put a dialog on the desktop.

    OpenKnowledge's own git commands now run fully non-interactively, so a missing or expired credential can no longer surface as a surprise system dialog. Instead it appears where it belongs: the sync status shows "reconnect to resume syncing" with a Sign in button, on your schedule rather than interrupting whatever you were doing. The same applies to opening a shared branch, where a credential dialog could previously outlive the fetch that raised it and sit on your desktop with nothing behind it. macOS and Linux were unaffected in practice, since their usual credential helpers never draw a window.

  • Fix the skills.sh page link on imported skills, and correct the write tool's skill bundle-path description.

    The provenance link in the Skills UI built its skills.sh URL as /<publisher>/skills/<skill>, but that path's middle segment is the source repo, not a literal skills. It resolved only for publishers who happen to name their repo skills; for everyone else it pointed at a page for a repo that doesn't exist. skills.sh answers those with a placeholder stub rather than a 404, so the link looked fine and quietly showed the wrong skill. It now derives from the recorded source via skillsShSkillLinks, which also covers website-catalog sources (/site/<hostname>/<skill>). Same correction applied to the import tool's source docs and the open-knowledge-write-skill guide.

    The write tool advertised skill bundle files as accepting "any path — references/**, scripts/**, assets/**, …", but only references/ and scripts/ are accepted; anything else is rejected. An agent following the description got an error contradicting it.

  • Fix the slash, wiki-link and tag pickers destroying typed text inside code blocks and inline code spans. All three menus used to open in a code context, and choosing an item deleted the characters you had typed there: the query vanished out of the fence and a chip landed after it, while a slash command replaced the whole code block with the chosen node and lost the fence's language tag. The menus now stay closed inside code, matching every other typing shortcut in the editor.

  • The config-sharing "Local only" choice is now labeled Only me across every desktop surface that names it — the create/open-folder dialog's radio card (SharingModeField), the Settings → Config sharing card (SharingSection), and the explanatory info tooltip (ConfigSharingInfoTooltip). Both radio cards now carry the same description spelling out the consequence: your project's OpenKnowledge setup stays on this computer and is not committed to git.

    (The ok init CLI prompt keeps its own terminal-context wording and is unchanged.)

  • Picking a value in an agent thread's settings menu now applies. Choosing a model, permission mode, or reasoning effort from one of the menu's submenus closed the submenu and jumped focus to the message field without ever sending the change to the agent, so the setting silently stayed where it was. The composer card focuses its message field when you press its whitespace; because React delivers a portaled menu's events through the component that opened it, that handler was also claiming presses inside the menu. It now acts only on presses that land in the card itself.

  • Agent chat threads now scroll like a real chat client. The transcript keeps itself pinned to the newest output while an agent is streaming, but the moment you scroll up to read something it stops fighting you — and a button appears to jump back to the live edge when you're ready.

    Before After
    Scrolling up mid-stream was undone by the next chunk snapping you back to the bottom Scrolling up holds; new output no longer yanks the view
    No way back to the latest message except scrolling by hand A jump-to-bottom button returns you to the live edge
    Reopening a past thread dropped you at the very bottom Reopening lands you at the last turn, with the previous message in view

    Sending a message still brings you straight to the bottom so you see your prompt and the reply. Long threads with many tool calls also render more cheaply, since off-screen rows are no longer laid out until they scroll into view.

  • Two rough edges on the new text-file editing: large-but-ordinary files like an 800 KB lockfile now open editable (only pathologically large files stay on the read-only viewer), and the visual/markdown mode switcher no longer shows for code and text files, which only have one editing surface.

  • Show an Update button on OK's built-in skills, and make ok init --no-skills state that it opts out machine-wide.

    The built-in skills (open-knowledge-discovery, open-knowledge-write-skill) had no way to surface an available update. The server already synthesizes their provenance and supports re-pulling them, but the built-in preview tab hardcoded its header actions to nothing, so the hook that checks for updates never mounted. The tab now shows a source link and an Update button when the upstream copy differs. Updates stay manual — built-ins never auto-apply.

    ok init --no-skills reported skipped (opted out via --no-skills). Nothing was skipped: the decline is recorded against your home directory and both bundles are removed from every user-global skill directory, so running it once in a throwaway project turned the built-ins off for every project on the machine. The summary now says that, and names how to undo it.

    installUserSkill also enforces the opt-out itself rather than trusting each caller to check first, so a declined bundle can't be reinstalled by a caller that forgets the gate.

  • Fixed path displays mangling Windows paths that resemble escape sequences. A folder such as C:\Users\x64qa rendered as C:\Usersdqa in the "Will be created at" caption (and any other message that shows a path), because the localization runtime decoded \x/\u escape sequences inside interpolated values. Decoding now applies only to the translated message text itself; values pass through verbatim. The actual files on disk were always created at the correct path — this was display-only.

  • Declared three missing runtime dependencies in the Linux .deb package (libasound2, libcups2, libgbm1). All three are directly linked by the app binary but were absent from the package's dependency list, so on a minimal system the package installed successfully and the app then failed to start with no window and no error dialog. Systems that already have these libraries (most desktop installs) were unaffected.

  • Fixed the Create-new-project dialog showing "No location selected" instead of the remembered location on systems where the OS cannot resolve the Documents folder (seen on headless Windows Server sessions). The location probe no longer fails outright in that case: it uses the remembered parent when one exists, and falls back to <home>/Documents/OpenKnowledge otherwise.

  • Stopped building the Linux AppImage. Its launcher requires FUSE 2, which current mainstream distributions no longer ship, so the file failed to start out-of-the-box on stock systems (verified on Debian 13, both architectures). Linux ships as a .deb; updates will be delivered through the system package manager (apt repository), and the in-app auto-updater is now disabled on Linux accordingly. Users without install rights can use the npm CLI or the web app.

  • Color themes are now authored in base16, the format the wider theming ecosystem already uses, and they reach considerably more of the app.

    The built-in palettes (Dracula, Catppuccin Frappé and Latte, Monokai, Gruvbox, Solarized) are reproduced from their canonical upstream schemes, and Settings → Plugins → Themes gains a "Paste a base16 scheme" field: drop in any of the several hundred published schemes as YAML or JSON — in either the current Tinted Theming layout or the original one — and it applies as-is. The sixteen slots are also editable by hand. An existing custom theme built from the older six-color picker is upgraded automatically rather than reset.

    The reason for the format change is coverage. The previous palette used ad-hoc names like "surface" and "accent" that several surfaces had no way to consume, so picking a theme left them on hardcoded colors. base16's slots carry fixed roles that map onto those surfaces directly, and the following now follow the selected theme:

    • Source-mode syntax highlighting. The CodeMirror editor was pinned to one of two bundled palettes regardless of theme. This also covers the diff view, the text viewer, the text-file editor, and the Mermaid editor.
    • Terminal colors. All sixteen ANSI slots, so program output is themed, not just the terminal's background.
    • Callouts. All fifteen accent colors.
    • Fenced code blocks in the WYSIWYG editor, plus inline code, blockquotes, tables, links, highlight marks, and horizontal rules.
    • Lint squigglies, broken-link underlines, and the file tree's problem indicators.
    • html preview embeds, which now receive the host's live token values instead of a build-time snapshot of the default theme, so an embed matches the surrounding editor.
    • The desktop window chrome on Windows and Linux — the titlebar overlay and window background take the active theme's colors rather than staying neutral.

    Also fixes three keyframe animations (the sidebar push pulse and two settings flashes) that painted nothing in any theme, including the default one, because they wrapped a color token in hsl().

  • The Linux desktop build now also produces an .rpm package, so Fedora, RHEL, and other RPM-based distributions can install OpenKnowledge with their own package manager instead of unpacking a .deb by hand. The .rpm installs the same way the .deb does — it puts the app in the launcher, registers the openknowledge:// links, and puts the ok command on your PATH — and it declares its system library requirements under their Fedora/RHEL names so the package manager pulls in anything missing. Both x86_64 and aarch64 packages are built.

  • The agent thread composer no longer carries an add-context +. It opened a one-row menu whose only row was the comment queue, and that row was inert unless the queue had already loaded and had something in it, so in practice the button opened a menu that could not do anything. Attaching a batch there was also the weaker of the two paths: it folded the comments into your next message but left every thread queued, while the Comments panel's own Send to chat dispatches to the open session and actually closes the review requests. That panel is now the single way to send queued comments to an agent, and the composer's bottom bar is just the agent settings on the left with the context ring and send button on the right.

  • Switching to source mode no longer flashes the landing highlight on a block it could not verify, so a flash now always means you landed on the text you selected.

    The underlying mis-landing is unchanged for now: on documents whose editor block structure and markdown source disagree about how many top-level blocks exist (for example after deleting the paragraph between two bullet lists, which leaves two adjacent lists the markdown source cannot distinguish from one), the "View in source markdown" jump and the plain mode toggle can land one or more blocks past the intended target — one block for each such divergence in the document. Those unverified landings simply no longer paint the highlight.

  • Fixed a crash that could take down the whole app window, replacing it with the error screen. It hit when the set of open side panels changed — switching between files, clicking a folder, closing the right chat panel, or collapsing the right side panes — and was most likely with the document panel collapsed and a right-hand column open. The layout engine ended up validating a saved multi-panel split against fewer panels than were on screen and threw Invalid 2 panel layout: ....

    The underlying resizable-panels library is now patched to recover from that momentary mismatch — it re-derives a valid split for the panels that are actually present instead of throwing — so opening, closing, and switching panels no longer risks crashing the shell. In the rare case the mismatch occurs, the affected panel widths reset to an even split rather than being preserved.

  • Screenshots attached to a bug report now actually appear in the report. The previous release added this and it did not work: the screenshot was still left out of every report, silently.

    Before After
    Report says a screenshot was included; nothing visible The screenshot is displayed in the report

    The cause was a dropped field. The app asks "does this bundle include a screenshot?" and passes the answer along when sending, but the layer that hands the request from the window to the app's background process was rebuilding it field by field and had never been taught about that answer. The background process saw no answer, took that to mean the reporter had declined, and left the screenshot out. Because "reporter declined" is a perfectly normal outcome, nothing was logged and nothing looked broken.

    Unchanged: the screenshot is still opt-in via the checkbox in the report dialog, still previewed before you send, and unchecking it still means no screenshot leaves your machine. Re-sending an older report from the report list still sends it without the inline screenshot, since the capture is no longer in memory by then.

  • The agent thread's working row is now the OK mascot changing shape instead of a spinner. It holds each pose, changes into the next, and holds again, with a squash and a squint on top so a long wait reads as effort rather than a stall. The status line beside it says what the agent is actually doing when a tool is running (reading, drafting, retrieving) and otherwise drifts through a pool of phrases on a randomized beat, so the row keeps moving without ever implying progress it cannot see. The line fades between phrases rather than cutting, and the whole row fades in when a turn opens. Under reduced motion the mascot holds a single pose and only the fades remain.

  • Make project-wide Fix all fast and keep the app usable while it runs. On a 2,400-document knowledge base the Problems panel took several seconds to open in project scope and a full sweep took minutes with the app locked up for most of it; the panel now opens in well under a second and the same sweep finishes in around a minute, with the window on screen, and the app stays responsive throughout. File groups start collapsed, so the panel shows a list of files and their problem counts instead of mounting every problem in the project at once — a new control next to Refresh expands or collapses them all in one click. Progress now advances in batches rather than on every file, which also stops screen readers announcing thousands of updates during a sweep. The sweep paces itself and retries when the server is briefly at capacity, so it no longer starves other work happening in the project and no longer reports files as unfixable when they were only refused. A running sweep can now be stopped: previously the only way out of a Fix all started by mistake was reloading the page. Files already fixed stay fixed, and the panel re-checks the project so the remaining count is accurate.

  • Prose containing a brace expression — an MDX comment like before {/* note */} after, or any {…} span with a *, _, ~ or an HTML entity inside it — no longer accumulates backslashes. The serializer was escaping the inside of those spans as if they were ordinary prose, but the parser reads a matched brace pair as an opaque expression and never removes the escape, so each backslash was escaped again on the next pass: one delimiter went from \* to three backslashes, then seven, then fifteen, doubling forever. Because every mode toggle, reload, remote refresh and agent write re-derives the document, a paragraph could grow without anyone editing it. Those spans are now written out exactly as typed and are byte-stable from the first save. Documents that already picked up extra backslashes stop growing; their existing bytes are left alone rather than rewritten.

  • Renaming a file in the Files sidebar no longer doubles its markdown extension. The rename box hands you the whole filename with only the name part selected, so typing or pasting a name that already ends in .md left the original .md in place and committed notes.md.md. That produced two files for one document: notes.md.md is filed under the document name notes.md, which resolves back to a different file, so the next save wrote the same document out again as a plain notes.md beside it. Both names also land on one row in the sidebar, so only one of the two files was visible there. A typed .md or .mdx now replaces the retained one instead of stacking on it. Names that merely contain dots (v1.2.md, report.2026.md) and non-document files are untouched. This stops new doubled names from being created; .md.md and .mdx.mdx files already on disk are left alone and still need renaming by hand.