Skip to content

v0.10.0-beta.9

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 09 Jun 01:55
· 50 commits to main since this release
bcda1ea

First beta of the cycle — 26 changesets.

Minor Changes

  • Adopt GPL-3.0-or-later. Open Knowledge is now licensed under the GNU General Public License v3.0 or later: every package declares the license field, the GPLv3 LICENSE text ships in the npm tarball and the desktop app bundle, ok --version prints the standard GPL notice (copyright, free software, no warranty), and the desktop About panel carries the same notice.

  • Add ok <file> — open a single markdown file in the editor with zero project setup. Run ok notes.md (or ok open notes.md) on any .md / .mdx file and it opens in the WYSIWYG ↔ source editor.

    • Project-aware. When the file lives inside an existing Open Knowledge project (an ancestor .ok/), it opens that project focused on the file — the path is realpath-resolved first, so a symlink into a project routes correctly.
    • No-project mode. For a loose file, it opens an ephemeral single-file session: a throwaway server scoped to just that one file, with git, MCP, and agents off. Edits save straight back to your original file. No .ok/ or other state is written into your directory — all session state lives in a temporary directory that's removed when you close the window.
    • Opening never reformats. A file you open but don't edit is left byte-for-byte identical, even when it has an unstable markdown round-trip.
    • Desktop-first with a browser fallback. Opens in the Open Knowledge desktop app when it's installed; otherwise serves the editor in your browser (Ctrl-C to end the session). Launching from a closed app goes straight to the file in a single window — it doesn't reopen your last project alongside it.
    • Focused chrome. No-project sessions hide the file sidebar, tabs, project switcher, and Settings — just the file and the editor.

    Discoverable via ok --help.

  • Preview embeds can now reach the network. The code-block html preview iframe runs with an open network CSP, so embeds that load external stylesheets, fetch live data, pull map tiles / remote images, use web fonts, or embed third-party iframes (over https:/wss:/data:/blob:) render — Leaflet maps, live-data charts, and the like work out of the box. 'unsafe-eval' is not granted (the common libraries don't need it), and * / plaintext http:/ws: schemes are excluded.

    The iframe stays a sandboxed null-origin frame (sandbox="allow-scripts", no allow-same-origin), so an embed can reach the network but can never read the knowledge base, cookies, or auth — it widens network reach, not document access.

    Breaking: the preview.scriptSrc config field is removed — the preview network policy is no longer configurable. A stale preview.scriptSrc key is rejected loudly on config load with a migration message (and handled by ok config migrate), not a silent no-op. A future multi-tenant host that needs to lock the preview network down will do so with an operator-level control (an env / build flag the tenant can't edit), not a content-editable config field.

Patch Changes

  • Rename the gbrain starter pack to Entity vault (GBrain-compatible) and reposition it as a human cockpit for GBrain-style Markdown brains rather than a GBrain reimplementation. This is a breaking pre-release rename: the canonical pack ID is now entity-vault with no gbrain alias, so ok seed --pack gbrain no longer resolves — use ok seed --pack entity-vault. The pack picker, seed dialog, toast, and CLI list now show "Entity vault (GBrain-compatible)". Generated dossier templates are tightened for GBrain-compatible parsing — document-level title: frontmatter, an explicit --- timeline --- sentinel, and parseable - **YYYY-MM-DD** | source | @author — … Confidence: … timeline bullets with path-qualified [[folder/slug|Label]] links. The workflow doc moved from /workflows/gbrain to /workflows/entity-vault. OK edits and reviews the Markdown; Garry Tan's gbrain, if installed, can still import/sync the same vault — interop is Markdown + Git, with no deep integration implied.

  • Restore the repository field on the published CLI package, pointing at inkeep/open-knowledge. It was dropped when the mirror was re-pointed off open-knowledge-legacy, which left npm Trusted Publishing unable to verify provenance and failed every release publish with E422 ... "repository.url" is "".

  • Desktop: the main process no longer crashes with a recurring "A JavaScript error occurred in the main process — Error: write EPIPE" dialog after every window is closed. On macOS the main process outlives its windows, and the auto-updater's periodic timer keeps writing to the stdout/stderr inherited from the launching terminal; once that terminal closes, each write throws EPIPE, which — with no stream error listener — escalated to an uncaught exception and Electron's fatal-error modal, reappearing every few minutes until the process was killed. A broken-pipe guard installed at the earliest point of main-process boot now handles these benign writes at the stream boundary so they never become an uncaught exception, while genuine (non-broken-pipe) stream errors are still surfaced to the file log. The guard is logger-agnostic, so it covers every stdout/stderr writer in the main process, not just the updater.

  • Fix the editor toolbar's "Open with AI" menu freezing the rest of the app in the macOS desktop app. Once the menu became openable on the desktop host, its default modal behavior disabled pointer events on everything outside the menu while it was open. Because the menu lives in the macOS title-bar drag region — where the outside-click that normally dismisses a modal doesn't reliably reach the menu — the only way to close it was to pick an agent, and meanwhile the rest of the chrome (notably the bottom-left project switcher) couldn't be clicked. The menu is now non-modal: opening it no longer blocks the rest of the UI, and clicking anywhere outside dismisses it. Browsers (ok ui) are unaffected.

  • Fix the sidebar project switcher not opening, and not dismissing on outside click, in the macOS desktop app. Radix's DropdownMenu opens on pointerdown, but the desktop renderer does not receive real pointerdown events (only mousedown/click), so clicking the trigger did nothing; and because the menu was modal, clicking outside could not dismiss it either. The trigger now also opens from the click event on the desktop host (mirroring the editor's "Open with AI" menu), and the menu is non-modal so outside-click dismissal works and the rest of the UI stays interactive while it's open — matching the Cloud/Sync popover. Browsers are unaffected.

  • Fix the project switcher in the sidebar footer not opening in the macOS desktop app. Clicking it showed a press state but the dropdown never opened. A Radix tooltip recently added to the trigger (to show the project path on hover) interfered with the menu on the desktop host: once the tooltip had opened on hover, clicking the trigger raced the tooltip's teardown against the menu's open, so the menu never stayed open. The trigger no longer uses a Radix tooltip — the full project path is shown via a native tooltip instead — and the menu opens reliably.

  • fix(open-knowledge): source-view tables no longer render indented left of surrounding text

    GFM table rows in the Markdown source view rendered roughly 2ch to the left of the
    surrounding prose, headings, and lists. The source-polish view-plugin set the
    .cm-table-row / .cm-table-header line classes but never set the --list-hang
    variable that the base .cm-line rule consumes, so the base !important
    padding-inline-start overrode the standalone table padding while the table's
    negative text-indent still applied, pulling table lines into the gutter. Tables
    now participate in the same hanging-indent mechanism as lists and fenced code, so
    they line up with body text. (PRD-6922)

  • fix(sync): track push and pull errors separately so a sync error stops flashing in the popover

    The sync status popover would show a sync error (e.g. a failed push) for only a split second before it vanished. The engine stored the last failure in a single shared error field, so a successful fetch on the pull leg cleared a still-unresolved push error — and trigger('sync') runs push then pull, so the push error was wiped by the immediately-following fetch within the same "Sync now". The same alternation happened continuously under auto-sync.

    Push and pull errors are now tracked independently (pushError/pushErrorCode and pullError/pullErrorCode). A success on one leg only clears that leg's error, so a real push failure stays visible until a push actually succeeds (or sync is toggled).

    When both legs fail with the same root cause (e.g. an auth failure that blocks fetch and push alike) the popover collapses them into a single neutral line instead of repeating two near-identical messages. When the two legs fail for different reasons, each line is labeled "Push:" / "Pull:" so it's clear which direction failed. A lone failure renders unlabeled, with pull-specific copy for read-side auth failures.

  • Remove the dismiss X from the "Relaunching to install the update…" banner. That card is a terminal in-progress state shown the instant Relaunch is clicked — there is nothing to dismiss, and the card already disappears when the app restarts, so a manual close would only hide live progress. The armed "Version X ready to install" card keeps its X (the user can still close it and relaunch later); only the post-click in-progress swap is now non-dismissible, via a new dismissible flag on the update-notice shape.

  • Fix the log and telemetry file-sinks writing into a second .ok/ when content.dir is a sub-folder. Both per-machine sinks (.ok/local/logs/server-current.jsonl, .ok/local/telemetry/spans-current.jsonl) now anchor on the project root like server.lock / principal.json / state.json, so a project with e.g. content.dir: docs keeps a single .ok/ at the root instead of growing a second one buried inside the content sub-folder where backups, git, sync, and visual inspection wouldn't expect it. ok diagnose bundle reads the sinks and server.lock from the same project-root-anchored .ok/local/ so it keeps harvesting them after the move.

  • Open .base and .canvas files (Obsidian Bases / Canvas) directly in the read-only text viewer. Previously, clicking a [[file.base]] or [[file.canvas]] wiki-link opened a chooser pane; clicking "Open file" from the chooser then replaced the editor view with a raw 415 JSON error envelope from the API. Now both file types open immediately in the built-in text viewer (.canvas with JSON syntax highlighting) — no chooser, no broken "Open file". The "Open file" affordance for all other downloadable types (.docx, .zip, etc.) is also hardened: it now routes through the sanctioned asset-dispatch path (OS-handoff on desktop, new tab on web) instead of a same-frame navigation to the asset API.

  • Fix the editor toolbar's "Open with AI" menu not opening in the macOS desktop app. The header sits in a macOS title-bar drag region (-webkit-app-region: drag), and macOS swallows the pointerdown event on its children — even on the no-drag button — before the DOM sees it. Radix's dropdown opens on pointerdown, so clicking the button did nothing. The synthesized click still fires, so on the desktop host the menu now opens from the click instead. Browsers (ok ui) are unaffected and keep Radix's default behavior. This regressed when the button's redundant hover tooltip was removed, which had incidentally kept pointer events flowing to the trigger.

  • fix(open-knowledge): MCP writes reflect on-disk truth — reconcile out-of-band edits instead of silently clobbering them (PRD-6832)

    An OK MCP read could authoritatively return content older than the bytes on disk, with no warning. The root cause was upstream of the read: a doc loaded in the server holds stale in-memory CRDT state, an out-of-band edit (a script, git pull, manual edit) makes disk newer, and the next MCP write serializes the stale CRDT over the newer file — making disk itself stale, so the next read faithfully returns bad bytes. In one incident this gave an agent a stale spec scope and cost a multi-file revert.

    This change makes disk authoritative on the write/reconcile path:

    • Reconcile-before-apply (L1). Before a content write (write_document / edit_document / edit_frontmatter) or a rename applies, the server compares the on-disk bytes to the last-synced base and, on divergence, ingests the disk edit first (through the existing sanctioned file-watcher path) so the agent's edit lands on top of current reality. Both edits survive.
    • Store-time backstop (L3). For the residual few-millisecond window where disk changes between the reconcile check and the store, the store re-checks disk before overwriting. On divergence it aborts the overwrite (disk wins) and the handler returns a hard urn:ok:error:disk-divergence (409) — the agent's edit was NOT applied; re-read and retry (a retry re-applies exactly once). This is the only guard for undo / rollback, which have no L1.
    • Heads-up on reconcile. When a write reconciles an out-of-band edit, the success response carries a disk-edit-reconciled warning (structuredContent.contentDivergence) so the agent re-reads to see the combined result. Observational — the write still landed and both edits are on disk.

    Human-editor (browser) writes are unaffected: the backstop only fires on agent-triggered stores, so in-progress typing is never reverted.

  • fix(markdown): stop over-escaping phrasing-boundary whitespace character references

    A space or tab at a phrasing boundary (the edge of a paragraph, list item, or blockquote's inline content) was serializing to a backslash-escaped character reference (\&#x20; / \&#x9;) instead of a bare &#x20; / &#x9;. Per CommonMark the escaped form renders as the literal visible text &#x20; in GitHub, the docs site, and Open Knowledge's own re-parsed editor. escapeEntityAmpersands now leaves character references that mdast-util-to-markdown synthesizes for boundary-whitespace preservation un-escaped, while continuing to escape user-authored entities.

  • Allow digit-leading tags (like the year 2026) in a document's frontmatter tags: field. The property panel previously rejected them with "Tags must start with a letter…", even though a year is a legitimate tag. The frontmatter tag grammar is now more permissive than the inline #tag grammar: a frontmatter tag may start with a letter or a digit, while inline #tag in prose still requires a leading letter (so #123 stays plain text and does not collide with issue-reference conventions).

  • Fix exec MCP results overflowing the client's per-result token limit on mid-size reads. The visible body was serialized up to 3× per result — content[].text, structuredContent.text (the load-bearing cross-client mirror that Claude-class clients read when they drop content[].text), and a redundant structuredContent.stdout raw copy — while the soft cap budgeted only one copy. A markdown file comfortably under the cap (so exec reported stdoutTruncated: false) could still produce a result large enough that the client rejected it and spilled to a file, silently degrading an in-budget read.

    Two changes: (1) drop the structuredContent.stdout field. It was the original Claude-Desktop content[].text-drop workaround, fully superseded by the structuredContent.text mirror it predates; its bytes are a strict subset of text and nothing consumed it. (2) Recalibrate the soft cap to budget the realized 2× body duplication (content[].text + structuredContent.text) rather than a single copy — the per-string cap is now ~25 KB (RESULT_BODY_BUDGET_BYTES / WIRE_BODY_COPIES), so the doubled payload stays well under the client ceiling. Oversized reads now truncate with the existing <truncated: … re-run with a more-specific query> marker instead of silently overflowing. The cross-client content[].text + structuredContent.text mirror is unchanged.

  • Fix telemetry shutdown losing local spans when the OTLP collector is unreachable. shutdownTelemetry() now drains the local file-sink exporter on its own awaited promise before the provider-wide teardown, instead of relying on tracerProvider.shutdown(). The provider fan-out is Promise.all(...).then(resolve, reject), which short-circuits the moment the OTLP push processor rejects (a fast ECONNREFUSED when no collector is listening) — pre-empting the file pipeline's still-in-flight disk write. That dropped the very spans ok diagnose bundle exists to harvest and caused the intermittent Telemetry > Gate combination 4 failure. Draining the file sink first makes on-disk span capture independent of the push pipeline's fate; the whole sequence still rides the existing shutdown-timeout race, so a pathological filesystem stall cannot deadlock teardown.

  • Add a "New project" command to the Cmd+K command palette. It opens the same create-new-project dialog reachable from the File → Create New Project… menu and the project switcher. Desktop-only — hidden in the web host.

  • Fix inline markdown images with a doc-relative path (![](./assets/x.jpg) mid-prose) resolving against the SPA root instead of the document's folder. The inline image node-view now applies the same doc-folder base resolution the block image render path already uses, so a relative src no longer 404s until a hard reload.

  • feat(open-knowledge): editor footer counts scope to the selection

    The footer word / character / token counts now reflect the current text
    selection. When you select a passage in either edit mode, the counts switch to
    that passage and gain a "Selected" indicator; collapsing the selection reverts
    to the whole-document counts. Selection counts use the same visible-text
    semantics as the document counter (markdown syntax stripped), so the same
    passage counts identically whether selected in WYSIWYG or source mode.

  • Sidebar + share-dialog polish:

    • Custom Markdown sidebar icon (M↓) for .md and .mdx files, replacing Pierre's default glyph. Colored via the existing --trees-file-icon-color-markdown CSS variable so it tracks light/dark and selected-row treatment.
    • ShareBranchSwitchDialog: align Cancel button styling with the other dialog Cancel buttons (outline + font-mono uppercase). Give the secondary "Open in current branch" button the same uppercase font-mono treatment as the primary "Open in " button.
    • ShareBranchSwitchDialog: move the repository/file/branch metadata block from the dialog header into the body so it groups visually with the explanation paragraph.
    • ShareBranchSwitchDialog: fix a spurious scrollbar caused by inline <code> pills whose py-0.5 extends past the surrounding line-box (leading-6 on the variant paragraphs absorbs the inline padding).
    • ShareMetadataRows: bump value rows to text-1sm and label cells to text-xs for tighter hierarchy (affects both share dialogs that use this component).
    • ShareReceiveDialog: tighten the inline-<code> corner radius to rounded-sm.
  • Custom DMG install window: the mounted disk image now shows the standard "drag the app onto Applications" layout, with the app icon and the /Applications alias positioned over a background image.

    Configured via electron-builder's dmg block (installer-window size, icon coordinates, background). The committed background is a schematic placeholder; final branded artwork drops into packages/desktop/build/dmg-background.png + dmg-background@2x.png (540x380 / 1080x760). Iterate on the layout locally with bun run build:mac:unsigned.

  • feat(ok-desktop): show the release-notes update notice on every open project window

    The "Updated to Version X" release-notes notice previously appeared on only one project window when several projects were open together, so a user looking at a different project never saw it. It now appears on every open window, and the "Relaunch now" update banner does too.

    Dismissing the release-notes notice on one window (or letting it auto-expire after its one-minute window) now clears it on all of them, so the same FYI no longer needs swatting once per window. A project opened shortly after an update, while the notice is still live, also shows it.

    When both a "new version ready to install" banner and the release-notes notice are armed at once, the single-card behavior is unchanged: the relaunch banner takes precedence and the release-notes notice waits behind it.