OpenKnowledge

v0.32.0

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

Minor Changes

  • Add content rules (a markdown linter with native markdownlint support) and themes as a toggleable plugin, introducing the new Settings → Plugins area. Shipping the two together makes the Plugins taxonomy legible from day one: content rules are a project-scope plugin (shared via git) and Themes is a user-scope plugin (personal to this device) — the two kinds of plugin the system is built around.

    Content rules. Powered by markdownlint (the engine behind VS Code's markdownlint), the linter surfaces common issues — hard tabs, trailing whitespace, heading-increment skips, inconsistent list markers, and more — as non-blocking warnings, with a gutter marker, hover tooltip, and a one-click "Fix" for auto-fixable rules. WYSIWYG mode flags the offending block with the same wavy underline — or, for textless blocks like thematic breaks and images, an outline so the mark is always visible. Enable it per project in Settings → This project → Plugins.

    Your rules live in your project's own .markdownlint.{json,jsonc,yaml,yml} (or .markdownlintrc) file — the portable, ecosystem-standard config, discovered at the content root — not in Open Knowledge's config. With no file present, the default matches the VS Code markdownlint extension exactly: every rule on, except MD013 (line-length). The only OK-config surface is the plugin toggle (contentRules.markdownlint.enabled, project scope).

    Problems panel. A new Problems tab in the right-hand document panel lists every problem for the active document with a count badge, and clicking a row jumps to the offending spot: the exact line and column in source mode, the containing block in WYSIWYG. It reads the document's source CRDT directly, so problems show in WYSIWYG mode too — not just source mode; the WYSIWYG block markers lint those same source bytes, so byte-level findings (hard tabs, trailing spaces, blank-line runs) paint in both modes. A "This doc / Project" scope toggle runs a whole-project audit on demand (a refresh button re-runs it; nothing polls in the background), listing every violating file with its diagnostics and error/warning totals. Clicking a diagnostic opens the offending file and lands on the violation — the exact line in source mode, the containing block in WYSIWYG. Auto-fixable diagnostics carry a one-click Fix in every mode: the CodeMirror lint action in source mode, a Fix button on the panel row, and a Fix button in the WYSIWYG block's hover tooltip — each applies the fix straight to the document's source.

    Settings → Plugins. A new Plugins management page (under This project) groups plugins by scope — "This project — shared via git" (content rules) and "This device — personal" (Themes) — and each enabled plugin appears under a Plugins header in the settings sidebar with its own panel. Whole-project audits run from the Problems panel, not Settings.

    Full-catalog rule browser. The markdownlint panel lists every rule the installed engine ships (53 today), grouped by category, with search over id/alias/name and an "Only modified" filter. Rule metadata — names, descriptions, documentation links, option types — is generated from markdownlint's own config schema, so the catalog always matches the installed engine version. Toggle any rule on or off and the change lands in your native .markdownlint.json (created from OK's tuned defaults on first edit). Expanding a rule row reveals its documentation link and a typed field per option: switches for booleans, dropdowns for enums, number inputs honoring the schema's minimum/maximum, plain text for strings, and tag-pill lists for arrays — set MD013's line_length, MD003's heading style, or MD043's required headings without hand-editing JSON. The rare option shape with no generic control shows an "Edit in config file" chip.

    Themes as a plugin. The IDE color palettes (Dracula, Catppuccin Frappé, Catppuccin Latte, Monokai, Gruvbox, Solarized, and a six-seed-color Custom theme with live preview) now live under Settings → Plugins → Themes, and the Themes plugin itself can be toggled off — disabling it reverts the whole app to the default palette (including the pre-paint flash cache) without losing your saved choice. Palettes are personal, user-scoped preferences (appearance.colorTheme, appearance.customTheme, appearance.colorThemeEnabled) applied flash-free on launch; dark IDE palettes force dark mode so Tailwind dark: styles stay correct, and switching back to Default restores your saved light/dark choice.

    Native config, the markdownlint-cli2 way. Rules live in the project's own .markdownlint.{jsonc,json,yaml,yml} files and are honored exactly as markdownlint-cli2 would: full JSONC (comments, trailing commas, BOM), extends chains (relative file paths, escape-guarded; package refs declined with a clear diagnostic; extends: null accepted), and a per-directory cascade — the nearest config file on a document's path governs it wholesale, with no OK-flavored merging underneath. The VS Code-parity defaults apply only when a project has no markdownlint file at all, and the first rule edit from Settings materializes them into a real .markdownlint.json. Executable configs (.cjs/.mjs) are detected but never executed. Malformed configs and unresolvable extends surface loudly (Settings, ok lint, and the lint API) instead of silently falling back, with the underlying parse/read detail in the message.

    Edits are faithful to your file. Alias-keyed configs (line-length for MD013, case-insensitive, last matching key wins) are resolved with markdownlint's own semantics, and rule edits rewrite the key form already in your file instead of appending a duplicate canonical id. Toggling a rule in a .markdownlint.jsonc preserves your comments, and unrelated keys and formatting stay untouched. Severity strings ("error" / "warning") — valid in markdownlint's own schema — load and lint normally; the rule browser shows such a rule as enabled with a read-only severity chip, and discloses before an edit that saving replaces the severity string. Lint reads are symlink-safe: a path that resolves outside the content directory is refused, matching the escape protection on every other read surface.

    Headless + MCP. A new ok lint [path] CLI command lints the project (or a folder/file) from the terminal, with --fix to apply auto-fixable rules and --json for structured output — exits non-zero on problems, CI-friendly. A new lint MCP tool surfaces problems to connected agents — for a single document or as a whole-project audit, capped at 10 files × 10 diagnostics with explicit "… and N more" indicators and always-accurate totals. Passing fix: true with a document applies markdownlint's auto-fixes in place through the collaborative document — attributed to the agent and reflected in the live preview, exactly like the editor's Fix button, never an unattributed shell edit; it stays auto-approvable because the fix is a recoverable, shadow-versioned content write like write/edit. Agent write/edit responses also carry the written document's problems as advisory warnings, so agents see the same issues the editor shows without an extra round-trip. The lint HTTP API is grouped under /api/lint/*.

  • Hovering a link in the editor now shows a preview card. Links to other documents in the project get a doc card — title, folder path, tags, last-edited time, backlink count, and a short excerpt — built entirely from the local index and file contents: on by default, no configuration, and nothing leaves the machine. This is a hover-behavior change for existing users: resolved internal links previously showed only the URL pill, which is still there with all its actions — the card renders alongside it.

    External links get an opt-in preview card showing the destination's site name, page title, description, and favicon. It is off by default and per machine: enable it from Settings → This project → Link previews, which sets linkPreviews.enabled in project-local config (never shared with collaborators). When enabled, hovering an external link sends that link's URL to the destination site to fetch preview metadata — one request per previewed link, with no cookies or credentials attached. The opt-in is enforced by the local server (with it off, no external request is made), results are cached locally, requests to private or internal addresses are refused, and any preview that can't be fetched quietly falls back to the plain URL pill.

  • Report a bug from inside the app. OpenKnowledge Desktop gains a full in-app bug-report flow: open it from Help → Report a Bug… or the ⌘K command palette, add an optional note, and the app packages logs and system info into a single secret-redacted zip you can reveal in Finder and review before anything leaves your machine. By default, Send makes no network request — it opens a prefilled email draft to support@inkeep.com that names the saved report file for you to attach. When a bug-report intake endpoint is configured (OK_BUG_REPORT_INTAKE_URL), Send instead uploads the report privately to the OpenKnowledge team, falling back to the same email draft if the upload fails — nothing is ever sent without your explicit action. A successful upload returns a report reference you can quote when opening a public GitHub issue.

    The desktop app also detects crashes locally, first-party only (Electron's crash reporter runs with uploads disabled): if a window process dies or the app quit unexpectedly last session, it invites — never auto-sends — the same report flow, with crash context prefilled and an opt-in, default-off checkbox for attaching the crash dump. Error screens (per-document and a new app-wide boundary) gain a "Report this error" action wired to the same dialog.

    For programmatic use, the CLI package now exports collectReportBundle({ level, projectDir, note, redact, outputPath, extraFiles }), the leveled bundling API behind both the desktop flow and ok bug-report: level standard collects the bug-report set (logs, lock/spawn diagnostics, system info), full adds the diagnose superset (telemetry spans, live server state, runtime metadata) and degrades gracefully by omitting pieces that aren't available, and extraFiles attaches pre-collected artifacts verbatim (the crash-dump opt-in rides on it). ok bug-report output is unchanged — it now routes through the same API.

Patch Changes

  • Add GitHub Copilot CLI integration with MCP, skills, and terminal launching.

  • Fix: GitHub Enterprise Server hosts are no longer ignored (#597). Auth resolution, the sync token relay, and the push-permission probe now derive their host from the workspace's origin remote instead of assuming github.com: resolveAuth scopes gh detection to the origin host, the sync engine relays the origin host's token, origin classification recognizes GHES hostnames (with browsable remote links in the sync UI), the ok auth subcommands default --host to the workspace origin, and the app's account panel shows the identity for the host the workspace actually syncs with. Sharing remains github.com-only for now.

  • Literal non-breaking spaces (U+00A0) in document prose now survive WYSIWYG edits and every markdown re-parse, instead of silently becoming regular spaces. The pipeline's internal NBSP whitespace sentinel was removed; whitespace-only text nodes are preserved by the existing inline-whitespace char-ref mechanism, which now also covers NBSP at emphasis boundaries.

  • Inline images now render in desktop windows attached to a server the desktop did not spawn (MCP-autostarted or terminal ok start). Every server now serves content-directory assets (images, video, audio, PDF, file attachments) at their /<contentDir-relative> paths by default, through the same admission middleware ok ui uses (inline/attachment dispatch, fail-closed 404 for missing assets). Previously only desktop-spawned servers passed --serve-content-assets, so a desktop window attaching to an MCP-spawned server resolved image srcs against an origin that 404'd them, showing broken images for every inline image in the project. The flag is still accepted for compatibility; pass serveContentAssets: false to bootServer to opt out. Servers that don't serve the React shell keep the ok ui pointer hint in their catch-all 404.

  • Fix the branch-mismatch share dialog ("Open shared document") overflowing its modal when the shared branch has a long name. The switch action used to embed the full branch name ("Switch to <branch>"), which pushed the button row past the dialog edge and clipped the primary "Open in worktree" button. The action now reads simply "Switch branch" — the branch name is already shown in the dialog's Branch row and body — and the footer's action group wraps instead of overflowing when all three actions (Open in current branch / Switch branch / Open in worktree) are present.

  • Stable releases are now versioned as a single semantic-version bump over the previous stable, derived from the changeset delta between them, instead of stripping the -beta.N suffix from the promoted beta tag. Accumulated patch work now ships as its own patch (for example 0.30.1 → 0.30.2) rather than collapsing an entire beta cycle to one stable, and a minor changeset in the delta still bumps the minor. Promotion selection and version determination are also separated: a scheduled job selects the newest soak-proven beta, and the promote job — shared by manual and automatic promotions — computes the version.

  • The desktop app now checks for updates hourly instead of every 5 minutes. The 5-minute cadence was a temporary pre-release setting; hourly matches the steady-state interval comparable apps use.

    It also keeps the beta update channel well within GitHub's API rate limits. The beta channel resolves the newest build through a docs-site proxy that calls GitHub's unauthenticated releases API on a budget shared across the whole install base (keyed on the proxy's IP, not each client's), so how often clients poll feeds straight into that one budget. Hourly, with a wider jitter window to break fleet lockstep, keeps it comfortable. Stable is unaffected either way, as it resolves through GitHub's own "latest release" redirect with no API call.

    An automatic update may now be noticed up to about an hour later than before. Auto-download and install-on-quit are unchanged, and "Check for Updates" still forces an immediate check on demand.

View v0.32.0 on GitHub