OpenKnowledge

v0.45.0

Released Jul 30, 2026. Part of the OpenKnowledge changelog.

Minor Changes

  • Blank lines you type in the visual editor now stay where you put them. Previously the editor showed the space while you were typing and then quietly took it back — switching to markdown source and returning, reloading the page, or a teammate opening the same document all collapsed a run of blank lines down to one.

    Before After
    Press Enter three times, reload — one blank line Press Enter three times, reload — three blank lines
    The file on disk kept your blank lines, the editor did not show them The editor shows what the file actually contains

    This covers blank lines between top-level blocks, which is where they come from when you press Enter. Two places still collapse them, both deliberately: runs at the very top or very bottom of a document, and runs inside a list, a quote, or a table cell — where CommonMark treats the blank line as meaningful structure rather than space. Nothing new is written into your files: no &nbsp;, no <br />, just the newlines that were already there.

    Worth knowing: blank lines still render as nothing when a document is published to GitHub or any other CommonMark renderer. This makes the editor honest about your file, it does not add vertical space to published output.

  • Keep your place when switching between the visual and Markdown editors. Toggling modes now keeps the block you were looking at in view in both directions instead of dropping you at an unrelated scroll position, and a plain toggle no longer disturbs either editor's own selection.

    Add a View in source action that jumps from the visual editor to the exact Markdown behind what you're looking at — centered, briefly highlighted, with the caret placed ready to edit. Reach it from the selection bubble menu, the desktop editor right-click menu, or the keyboard: ⌥⌘M toggles between visual and source, and ⌥⌘E opens the source for the block at the caret. The highlight respects your reduced-motion setting.

    Landing is honest about uncertainty rather than confidently wrong. If the document changes underneath a pending jump, or a block cannot be matched exactly, it settles on the nearest enclosing block and drops the highlight instead of guessing. Long documents keep working: the landing waits for the incoming editor to actually render, re-aims itself if content shifts while it settles, and gives up cleanly rather than fighting the page.

    Explicit navigation always wins over a mode switch that is still settling. Clicking a Problems-panel row, an outline entry, or a find/replace match takes you there even immediately after a flip, and the composer's scroll-to-bottom no longer pulls you off a landing.

  • Underline is now a real, savable format. The bubble-menu Underline button and Cmd+U have always been there, but nothing wrote the result to disk: the underline vanished the moment the document was saved, reloaded, or opened by a teammate. Underlined text pasted in from Word arrived as italic instead.

    Before After
    Cmd+U, reload the page — underline gone Cmd+U, reload the page — still underlined
    Paste underlined text from Word — comes in italic Comes in underlined
    Paste underlined text from Joplin or Logseq — formatting dropped Comes in underlined

    Underline is written as <u>text</u>, the same thing Typora, MarkText and Obsidian write, so files stay readable and portable. Documents that already spell it <ins>text</ins> — what Joplin and Logseq produce — are read as underline too and keep that spelling when saved, so importing a vault does not rewrite it under you.

    Worth knowing: markdown has no underline of its own, so this is HTML inside your file. It renders as underline in OpenKnowledge, in a published site, and when pasted into Word, Docs or Gmail. GitHub is the exception — it strips <u>, so the text shows up unformatted there. Files that use <ins> do keep their underline on GitHub.

    Underline can be combined with bold, italic, strikethrough and inline code on the same text.

Patch Changes

  • Screenshots attached to a bug report now show up in the report itself, instead of only inside the downloadable diagnostic bundle. Anyone triaging a report sees the picture immediately rather than having to download a zip and unpack it to find out what the reporter was looking at.

    Before After
    Report says a screenshot was included; nothing visible The screenshot is displayed in the report
    Triager downloads the bundle and unzips it to see anything Nothing to download for a look at the screen

    This was supposed to work already, and never did once. The intake tried to pull the screenshot out of the bundle after the upload, but the storage the bundle lands in refuses that read, so the step failed silently on every single report and the screenshot was quietly left out. The app now uploads the screenshot alongside the bundle rather than expecting the server to dig it back out, which removes the failing step entirely.

    Two things worth knowing. This takes effect for reports sent from this version onward, because the app is what supplies the screenshot now — reports already filed are unaffected. And re-sending a report from the report list in a later session sends it without the inline screenshot, since the capture is no longer in memory by then; the screenshot is still inside the bundle in that case.

    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.

  • Version history that a consolidation had folded away could be destroyed by routine checkpoint cleanup. Consolidation picked the single newest checkpoint to chain onto, without regard to what kind it was. The silent rescue checkpoints written when the editor bridge detects content loss are parentless, so whenever one of them happened to be newest, the new consolidation chained onto a dead end and the previous consolidation was left dangling. The next cleanup pass then deleted that dangling checkpoint's ref, and with nothing else pointing at it, its full content snapshot and every edit it had folded in became unreachable and were lost at the next git garbage collection. A same-second pair of consolidations could fork the chain the same way with no rescue checkpoint involved at all.

    Consolidation now adopts every dangling anchor rather than the single newest one, and only checkpoints whose retention cannot be emptied are eligible to anchor. Cleanup is non-destructive again. Two side effects of the old behavior are also fixed: a rescue checkpoint is no longer kept alive forever by being chained onto, so the retention budget that expires it now genuinely does, and the per-document activity history no longer goes silently empty after a rescue checkpoint lands following a consolidation.

  • Show comment annotations dimmed in the editor instead of hiding them.

    Text that looks like comment syntax (<!-- note -->, %%note%%) is claimed by the comment promoter the next time a document is re-derived. The resulting comment mark and comment block rendered display: none, so prose a user typed as ordinary text disappeared from the editor while the bytes on disk stayed intact, with no way to recover it from the WYSIWYG.

    Comments now render dimmed in the editing surface, with the delimiter form shown alongside the body and a dashed rail plus gutter marker on the block form so it reads as annotation rather than as a blockquote. The app's read-only views that mount the editor schema (skill viewer, rendered diff) show comment bodies dimmed too; only fully rendered markdown output still hides them, as literal HTML comments. Comments are still dropped from cross-app clipboard payloads, and bytes are untouched in both directions.

    To turn an annotation back into ordinary text, edit the delimiters in source mode (Cmd+/) — backslash-escaping either delimiter (\%\%) keeps the run as prose. There is no WYSIWYG control for this yet.

  • Keep backlinks, graph data, and tags consistent across every document lifecycle path. Direct creates and duplicates, disk reconciliation, deletes, folder renames, project-skill moves, ignore-file changes, startup, and branch switches now pass through one ordered derived-index coordinator with shared admission, readiness, persistence, and invalidation rules.

    Derived-view queries wait for startup and branch replacement to settle instead of observing a partially rebuilt index. Rapid consecutive branch switches are serialized so one transition cannot release another transition's barrier.

    Durable creates, duplicates, renames, deletes, and persistence stores now return according to authoritative disk state. Derived projection and cache persistence remain best-effort, so coordinator shutdown or a delayed cache save cannot turn an already successful write into a failure.

  • Fix multiple self-closing MDX JSX tags on the same paragraph collapsing to text. The R23 autolink/void-HTML guard classified any <X… as self-closing when the paragraph contained any />, so A <Foo />, B <Bar />, C <Baz />. promoted every < up to the last one and lost the intermediate tags. It now decides self-closing per-tag by scanning that tag's own contents (quote- and brace-aware), skipping < characters that fall inside another uppercase tag's attribute region. Orphan uppercase openers stay guarded; quoted > / /> and <X> inside JSX expression braces round-trip cleanly.

  • Fix the outline not following your scroll position in Markdown source mode

    The outline pane now highlights the heading you are reading while you scroll the Markdown source view, the same way it already did in the rich-text view. The position marker, the highlighted row, and the row's accessibility state all keep up as you scroll, and clicking an outline row leaves that heading highlighted once the view settles.

    Headings written with Windows line endings are also recognized now. Previously a document saved with CRLF line endings produced an empty outline.

  • Two more places where the editor could replace what you had open with the copy on disk now leave a restorable version behind first.

    The first is a conflict between an agent and something else writing the same file. When an agent saves a document that another program changed underneath it, the copy on disk wins — that part is correct and unchanged, and the agent is still told its write did not land. But the open document was replaced outright, and anything you had typed into it that had not saved yet went with it, silently and with no way to undo. The second is the same situation for a skill or template edited from two places at once.

    Both now write a recovery checkpoint of the open document before replacing it, so what you had is restorable from version history, and both record the event so it shows up in a diagnostics bundle rather than disappearing without trace.

  • The Problems panel now offers a Fix all with AI button beside the deterministic fixer, in both the document and project scopes. It hands the whole scope to your preferred AI agent as one instruction, pointing it at the audit tool for the current list of lint violations and broken links and at lint with fix: true for the mechanically-fixable subset, so the agent reads live truth rather than a snapshot taken when you clicked.

    The deterministic button is now labelled Auto-fix rather than "Fix all", and both buttons explain themselves on hover. Previously a document whose problems had no automatic fix showed a single disabled "Fix all" with nothing to say for itself, which read as a bug rather than as "these need judgement"; now the AI action sits next to it, enabled, and the disabled Auto-fix says why it is off.

  • The Problems panel now names the validator behind each finding instead of tagging every row LINT — a frontmatter-schema failure reads FRONTMATTER and a markdownlint rule reads MARKDOWNLINT, so the two are distinguishable at a glance (the chip carries the producer, so the line below it now shows the bare rule code). Repeated findings collapse into one row with an instance count ("Frontmatter property is missing · 10 instances") that expands to the individual lines, each keeping its own Fix and Ask AI actions, instead of burying the rest of the plane under identical rows. The project-scope refresh icon finally explains itself on hover.

  • Clicking Edit next to a frontmatter schema in Settings now reliably opens the Fields editor. Previously it could drop you on the read-only Source view instead, which was especially confusing when the schema was already the file open behind the Settings dialog, and the ignored request could then send you to Fields the next time you opened that file when you had asked for Source. Edit now takes effect whether or not the schema is already open.

  • Stop two typed editor states from losing content on the next re-derivation.

    A hard break typed at the end of a block (Shift+Enter) was dropped at serialize time, so the break vanished with no trace in the bytes and none in the view. It is now re-spelled to <br />, the same lossless spelling the serializer already uses inside table cells and headings.

    An empty task item lost its checkbox and came back as a plain list item whose text was the literal [ ], at every position in the list. GFM cannot spell a task item with no content, so an empty one now serializes with a &#x20; the parse side takes back out, and the checkbox round-trips.

  • Sharing a folder with no documents in it now says so plainly instead of promising the link will work after the next sync. Git has no way to represent an empty folder, so the minted link 404s for the recipient and no push would ever fix it — but the share popover offered "Enable auto-sync", "How to push manually" and "Sync now", and running a sync turned the warning into "Synced. The link is up to date." over a link that still went nowhere. The popover now shows a warning naming the only real remedy, adding a document, and offers no sync buttons at all. The share_link MCP tool relays the same sentence to agents rather than the push advice, and the freshness signal on POST /api/share/construct-url gained an empty verdict so the two cases are also tellable apart in the logs. Folders that genuinely just need a push are unaffected, and a doc reports exactly as before at any size, including zero bytes.

  • On Windows, a project inside a git repository now correctly places .ok/ at the repository root when the path contains a shortened folder name. Windows keeps a legacy short alias for most long folder names — C:\Users\alexandra is also reachable as C:\Users\ALEXAN~1 — and the two spellings point at the same folder but read as different paths. OpenKnowledge compared the shortened form against the long form that git reports, concluded the repository was outside your home directory, and quietly skipped the promotion, leaving .ok/config.yml in the sub-folder you picked instead of at the repository root. Both spellings are now resolved to the same canonical path before the comparison, so the repository root is used as the project root as intended. Unaffected on macOS and Linux, which have no such aliases.

View v0.45.0 on GitHub