v0.5.0
Part of the OpenKnowledge changelog.
Minor Changes
Desktop now reclaims project-local MCP files and SKILL files on project open, force-writes user-level SKILL files on every launch, and renames-aside corrupt MCP configs (both project and user scope) so a fresh canonical config can be written.
Project open (new): every supported editor's project-local MCP config (
.mcp.json,.cursor/mcp.json,.codex/config.toml) is checked alongside.claude/launch.json. ExistingmcpServers.open-knowledgeentries get rewritten to the bundled CLI; absent entries / absent files stay absent (namespace ownership). Existing project-localSKILL.mdfiles under.claude/skills/open-knowledge/and.cursor/skills/open-knowledge/get replaced with the bundled version; absent files stay absent.Every launch (changed): user-level
~/.agents/skills/open-knowledge/always force-writes to the bundled version, and per-host copies (~/.claude/skills/open-knowledge/,~/.cursor/skills/open-knowledge/) force-write when the host directory exists. This replaces the priornpx skills addsubprocess path that silently spawn-errored under macOS GUI launches (Dock click, LaunchServices) becausenpxtypically isn't on the GUI process's PATH.~/.ok/skill-state.ymlnow advances on every cold launch instead of getting stuck on whichever version a past terminal-invokedok initrecorded.Corrupt MCP configs (new): if a project-local or user-level MCP config file (
.mcp.json,.cursor/mcp.json,.codex/config.toml,~/.claude.json, etc.) is unparseable OR blank/whitespace-only, the desktop renames it to<configPath>.broken-<isoTimestamp>and writes a fresh canonical config in its slot. This unblocks recovery from accidentally-truncated configs without forfeiting the original bytes. Valid files without anopen-knowledgeentry are still left alone (no clobbering of unrelated tools' configs). The CLI gains a newclassifyExistingMcpEntryhelper exporting the discriminated{ kind: 'absent' | 'no-entry' | 'present' | 'corrupt' }outcome that drives the new logic..claude/launch.jsonforce-write (changed): on every project open, the desktop unconditionally invokesscaffoldLaunchJsonrather than gating on namespace ownership.scaffoldLaunchJsonis already merge-aware — it creates the file when absent, adds theopen-knowledge-uiconfiguration when missing (siblings preserved), replaces the entry when present, and gracefully handles blank/whitespace files by writing a fresh one. This brings launch.json behavior in line with the user-level SKILL force-write posture so blanking the file produces a working launch.json on next project open instead of a no-op. Only genuinely-corrupt JSON (invalid syntax) still fails — siblings the user authored into a broken file outweigh the recovery-on-corrupt case here, so no backup-and-rewrite for launch.json.Packaged macOS Desktop now reclaims existing Open Knowledge integration namespaces to the installed bundle on startup/project open. Existing
mcpServers.open-knowledgeentries are rewritten to the bundled CLI, existing Claude Codeopen-knowledge-uilaunch configs are rewritten took uivia the bundled CLI, and terminal shims are installed at~/.ok/binthrough a managed~/.ok/env.shshell-rc block instead of an automatic/usr/local/binadmin prompt.If your real interactive shell
PATHincludes additional writable non-system directories (for example~/binor~/.local/bin), Desktop may also create or refresh OK-ownedok/open-knowledgesymlinks there; foreign entries are never overwritten. If you use custom wrappers, register them under a different MCP server or launch-config name. The menu-driven “Install Command-Line Tools…” action remains available for users who explicitly want/usr/local/bin/{ok,open-knowledge}symlinks.Add
discoverMCP tool — brownfield project convention extraction + link-graph activation.Joins
ingest/research/consolidateas the fourth workflow-style tool that returns a multi-step instructional body with STOP gates. Whereok seedscaffolds greenfield repos with the Karpathy three-layer,discoverhandles brownfield: connect OK to a repo with existing markdown content and the agent extracts conventions from sibling docs, sets folder frontmatter + templates, curates.okignore, and activates the link graph (orphan triage, hub identification, untextualized + vague-referential cross-references viasuggest_linksandsearch). Seven phases with per-phase user-confirmation gates; idempotent on re-run.Composes existing primitives only (
set_folder_rule,write_template,get_orphans,get_hubs,get_dead_links,suggest_links,edit_document,list_documents,exec,search) — no new MCP primitives, no schema changes. Phases 1-4 run fs-direct without the Hocuspocus server; Phase 5 (link-graph activation) requires the server.Plus seven surgical SKILL.md edits make the tool discoverable from inside the bundled Agent Skill (Workflow tools table row, anti-pattern row, three section additions, two paragraph updates). SKILL.md stays under the 40,000-char hard cap.
Spec:
specs/2026-05-13-discover-tool/SPEC.md.Project creation under a directory whose parent contains
.git/now opens the parent (the git working-tree root) AND defaults the content scope to that same parent — opened folder andcontent.diralign by default. Previously the parent was opened butcontent.dirwas silently scoped to the picked sub-folder, so the user landed in the git root visually while writes were narrowed beneath it.Surfaces the WHY in three places:
- CLI (
ok init):[ok] Initialized OK at <gitRoot> — opened parent of <cwd> because it contains a .git folder(was(scoped to <subPath>/)). - Desktop toast on freshly-spawned editor windows:
Initialized OK at <gitRoot> — opened parent of <pickedPath> because it contains a .git folder(toast IPC payload'scontentDirfield replaced withpickedPath). - Desktop consent dialog banner: explicit "parent of
<pickedPath>because it contains a.gitfolder (one .ok/ per git repo). The default scope is the entire repo; change Content directory below to narrow it."
Narrowing back to the originally-picked sub-folder is still a one-edit choice — the consent dialog's Content directory field is pre-filled with
.and accepts any sub-path, andconfig.yml'scontent.dirremains the post-init knob.- CLI (
feat(mcp):
ingestbiases toward downloading raw binary filesAgents calling
ingest("<binary-URL>")from a shell-capable host now preserve the raw bytes of the source — PDFs, images, audio, video, Office docs, archives — underexternal-sources/<slug>.<ext>alongside a markdown wrapperexternal-sources/<slug>.mdwhose body is a single![[<slug>.<ext>]]wiki-embed. Previously the tool's plan body directed agents to "use your available web fetch tool," which on every host is an LLM-mediated text extraction that loses figures, tables, layout, OCR, and embedded metadata.The mechanism stays planner-only: the MCP tool returns a procedural plan, and the agent executes it via its shell tool (
curl -L --fail --max-redirs 5 --max-time 60 --max-filesize 104857600 -o ...). OK's file-watcher picks up the dropped binary as anasset-createevent identically to a manual drop. The wiki-embed renders inline for images / video / audio and as a Notion-style File row for PDFs and opaque attachments (the pdfjs canvas viewer remains opt-in via the explicit<Pdf src="...">JSX form).New STOP gates in the plan body:
- Streaming-video / DRM media (heuristic — YouTube, Vimeo, Spotify, Twitch, anti-bot signatures) STOPs with a
yt-dlppointer rather than failing into anti-scraping. - Executable / scripted-document extensions (
EXECUTABLE_BLOCKLIST_EXTENSIONSfrom@inkeep/open-knowledge-core) hard-block — the ElectronopenAssetSafelyruntime backstop is desktop-only, so plan-level enforcement is the only line of defense in CLI / web / Cowork contexts. - Size pre-check via HEAD Content-Length: 100 MB hard cap (GitHub's commit-cap is the binding constraint since
external-sources/is committed to git by default), 50 MB warn tier. - Re-ingest with sha256 mismatch: new dated sibling (
<slug>.YYYY-MM-DD.{ext,md}) withsupersedes:frontmatter pointer. Old wrapper + binary untouched — append-only invariant perexternal-sources/**layer convention.
Shell-less hosts (Claude.ai web; possibly Cowork — verify per host) fall back to the existing text-extraction flow and set
preservation: text-onlyin the wrapper's frontmatter, plus prepend an admonition warning that the binary was not preserved. Downstream tooling can grep the flag to detect docs that should be re-ingested from a shell-capable client.Wrapper frontmatter declares the full
tags: [source, immutable, layer-ingest, binary]list explicitly — the legacy.ok/config.yml folders[]cascade mechanism is no longer honored by the runtime config loader (folder defaults today live in opt-in nested<folder>/.ok/frontmatter.yml), so the wrapper does not rely on a cascade that may not exist.Bundled
SKILL.mdupdated in lock-step: §Media now distinguishes free-form image-embed placement (co-located alongside the referencing doc) fromingest'sexternal-sources/layer-1 convention; §Workflow tools'ingestrow references the binary-preservation flow; §Frontmatter conventions document the binary-source wrapper shape.Full SPEC + decision log:
specs/2026-05-19-ingest-prefer-binary-downloads/SPEC.md. Architecture stays planner-only (NG1) — server-side fetch is documented as a future-work relief valve in §15 if dogfood shows the shell-less degraded path is a meaningful product gap.- Streaming-video / DRM media (heuristic — YouTube, Vimeo, Spotify, Twitch, anti-bot signatures) STOPs with a
BREAKING: Mermaid diagrams are now authored exclusively as
```mermaidfenced code blocks. Existing<Mermaid chart="…" />JSX must be manually converted to a mermaid fence — legacy JSX will display as an editable source block instead of a rendered diagram.To migrate: search your content for
<Mermaid chart=and rewrite each occurrence as a```mermaidfence. Untouched legacy content remains visible and editable as an unknown-component source block, but no longer renders as a diagram until converted.Mechanism: the canonical descriptor was renamed
Mermaid→MermaidFenceso legacy<Mermaid />JSX no longer matches at parse time and falls through to the unknown-component editable source block (no silent migration on save). Theidandthemeprops were dropped (neither is expressible in fence syntax). Slash menu and PropPanel continue to show "Mermaid" viadisplayName.feat(ok-mcp-additions): three additive MCP tool surface changes plus a preview-url correction. Closes feedback-driven gaps from the 2026-05-15-ok-feedback-gaps parent spec: per-key frontmatter editing via JSON Merge Patch, build-time generation of the MCP
instructionsecho from canonical SKILL.md, and workflow-tool DESCRIPTIONs that lead with the "returns procedural guidance, not data" contract. The fix-up commit also drops a misleadingopenknowledge://deep-link short-circuit from the MCPpreviewUrlfield that external in-app browsers cannot render.@inkeep/open-knowledge-core— newFrontmatterPatchRequestSchema+FrontmatterPatchSuccessSchemainpackages/core/src/schemas/api/agent-write.ts; newurn:ok:error:invalid-frontmatter-patchcode inpackages/core/src/schemas/api/_envelope.ts. The patch request schema enforces RFC 7396 JSON Merge Patch semantics ({key: value}sets,{key: null}deletes); per-key values flow through the existingFrontmatterValueSchemaso atomic rejection with per-keyfieldErrorsis consistent with the rest of the agent-write surface.@inkeep/open-knowledge-server— three MCP tool surface changes plus a generated artifact:frontmatter_patch(revived,packages/server/src/mcp/tools/frontmatter-patch.ts) — previously parked, now re-enabled with a server-side CRDT path inapi-extension.ts. Opens a DirectConnection, applies JSON Merge Patch logic inside onedc.document.transact(fn, session.origin)block (mirrorsapplyAgentMarkdownWriteInneratagent-sessions.ts:124-184). Atomic rejection on schema failure viaeditErrorcapture; routes throughcomposeAndWriteRawBody(paired-write enforcement). Concurrent patches to different keys merge via Y.js field-level CRDT; concurrent patches to the same key resolve last-writer-wins per Y.js semantics.- Workflow-tool DESCRIPTION rewrites for
ingest,research,consolidate,discover— each now leads with "Returns a multi-step plan for ..." framing to fix the cross-round naming-expectation pain (F1, C4, E7 in the parent spec's feedback rounds). Tool names unchanged (DD-5 LOCK B). - Build-time generation of
instructions.ts— newpackages/server/scripts/generate-instructions.tsextracts 4 H2 sections from canonical SKILL.md by exact heading-text match (STOP, Reads-examples, Preview, Scope recap); concatenates with one-line identity prefix and pointer suffix.instructions.tsis now a generated artifact;bun run checkgates drift viascripts/check-instructions-drift.sh. Eliminates dual-source-of-truth drift between SKILL.md and the MCPinstructionsecho. previewUrlresolution simplified (packages/server/src/mcp/tools/preview-url.ts) —OK_ELECTRON_PROTOCOL_HOSTshort-circuit dropped from the resolver.previewUrlis now alwayshttp://localhost:<ui-port>/...ornull; theopenknowledge://URL scheme stays load-bearing for OS-level deep-linking (URL-scheme handler, dock drag, sidebar pills, second-instance argv) but is no longer emitted as an MCPpreviewUrl— external agent in-app browsers (Claude Desktop, Cursor, Codex) cannot render custom URL schemes. Under OK Electron the resolver currently returnsnulluntil a follow-on spec exposes the React shell over HTTP from the utility process; once that lands, the lock branch fires universally andpreviewUrlis non-null for every running topology.
@inkeep/open-knowledge— the CLI's bundled MCP server picks up all three tool changes via the existingregisterAllToolsshim inpackages/cli/src/mcp/tools/index.ts. No CLI subcommand changes.
Full parent spec + decision log:
specs/2026-05-15-ok-feedback-gaps/SPEC.md. Sub-spec B:specs/2026-05-15-ok-mcp-additions/SPEC.md.Remove the user-global template tier (
~/.ok/templates/) — breaking schema changes.Templates now live exclusively at the project + folder tier (
<folder>/.ok/templates/<name>.md); the user-global cascade hop is gone. Five breaking changes ship together:- MCP
write_template/delete_templatelosetarget. The InputSchema is now.strict()-wrapped, so callers passing anytargetvalue (including'project') — or any field not in the schema — get a Zod parse error citing the unknown key instead of a silent drop. Update LLM agents and automation that previously senttarget: 'user'ortarget: 'project'to drop the field entirely; the project-tier path is now the only path. - HTTP
PUT /api/templaterejects legacytargetbody field. The request schema is.strict(); sending{ ..., target: 'user' }returns 400 RFC 9457 with the field named indetail.GETandDELETEaccept?target=...as an unknown query and silently ignore it — same as any other unknown query param — and serve the project-tier resource. Include personal templatescheckbox removed from the in-app SeedDialog;ok seed --personal-templatesflag removed from the CLI. Commander rejects legacy invocations with an unknown-option error.- Personal-templates pack content deleted entirely from source. The 8 templates (daily-journal, meeting-notes, weekly-review, reading-log-entry, gym-log, recipe, travel-trip, morning-pages) and the surrounding plan/write infrastructure are gone. No in-tree stash — recover from git history at the previous beta tag if you ever need the strings.
- Desktop-bridge contract narrows.
OkScaffoldPlan.personalTemplates,OkSeedApplyResult.personalTemplates,OkSeedPlanOptions.includePersonalTemplates, andOkSeedApplyOptions.includePersonalTemplatesare removed acrosspackages/core,packages/app, andpackages/desktop.
Existing
~/.ok/templates/*files become inert (no read path references them; no migration ships). Move any templates you want to keep into a project's.ok/templates/.Locality principle behind the removal: see
PRECEDENTS.md#50 (clone-behavior test).- MCP
Patch Changes
Hide the View → Reload / Force Reload / Toggle Developer Tools cluster in stable desktop builds. Beta DMGs keep the cluster (they're built from the same legacy commit with version
X.Y.Z-beta.N) so we can still debug shipped beta; stable builds (where promotion overrides the version to plainX.Y.Zvia--config.extraMetadata.version) no longer expose the dev tools entry points. Dev (bun run dev) is unaffected.Navigator window polish:
- Set minimum window dimensions on the editor and navigator (Switch Project) windows. Previously both
BrowserWindowconstructors omittedminWidth/minHeight, so Electron applied its 0 default and users could drag the windows to almost nothing. Editor clamps at 720 × 480; navigator clamps at 640 × 560. - Retune the navigator's default size from 800 × 750 (aspect 1.07, awkwardly square) to 840 × 600 (aspect 1.40, list-friendly landscape) — better matches the category convention (Xcode, VS Code, IntelliJ all landscape) and gives the recent-projects list a more proportional shape.
- Fix the empty / first-launch state: with zero recents, the chrome row (header) and content row (3 cards) now center vertically as a single group instead of leaving the header pinned to the top with the cards floating in a void below it. Trade-off: in the empty state, the macOS title-bar drag region moves with the chrome row to its centered position; users drag the window from the centered header rather than the top edge. Traffic-light buttons remain functional. The 4 chrome-row drag invariants pinned in
NavigatorApp.test.tsare preserved.
Initial editor size (1280 × 800) is unchanged.
- Set minimum window dimensions on the editor and navigator (Switch Project) windows. Previously both
ok diagnose process <pid>now explains why a pid is rejected (Invalid pid '<value>': must be a positive integer) instead of the bareInvalid pid: <value>— matches the format of the sibling--cpu-profilevalidation message in the same command.fix(desktop): treat
ERR_UPDATER_CHANNEL_FILE_NOT_FOUNDas "no update available" instead of an alarming 404 dialogA beta build's menu-driven "Check for Updates…" could surface a multi-paragraph HTTP-404 dump (
Cannot find latest-mac.yml in the latest release artifacts (…/v0.5.0-beta.22/latest-mac.yml): HttpError: 404 …) when the latest prerelease tag existed but its DMG + channel manifest hadn't been uploaded yet.release.ymlcreates the prerelease GitHub Release first;desktop-release.ymlruns second and takes ~8 min to build, sign, notarize, and uploadbeta-mac.yml+ the DMG. During that race window the release appears inreleases.atom(so electron-updater picks it up as the latest matching tag) but the channel manifest 404s. electron-updater's GitHubProvider also falls back frombeta-mac.yml→latest-mac.ymlwhenallowPrerelease=true, so the final user-visible URL nameslatest-mac.ymleven on the beta channel.onErrorinauto-updater.tsnow special-caseserr.code === 'ERR_UPDATER_CHANNEL_FILE_NOT_FOUND'on menu-driven checks: the friendly "You're on the latest version" dialog fires instead, and the classified-warn log still records the code + URL for operator triage. The next periodic check (default ~5 min) re-fetches and surfaces the genuine update once the manifest lands. Other classified codes (HTTP_ERROR_*,ERR_UPDATER_ZIP_FILE_NOT_FOUND,ERR_UPDATER_INVALID_UPDATE_INFO, …) still surface as error dialogs — they describe real failures, not a transient empty-release state.Fix Claude Code preview pane breaking when port 3000 is occupied by an unrelated dev server.
ok uinow defaults to binding port 39847 with kernel-allocated fallback onEADDRINUSE, andok initwrites port 39848 plusautoPort: trueto.claude/launch.json. The deliberate gap between the two ports routes Claude Code's preview spawn through the lock-collision proxy mode (which empirically works) rather than the same-port "already-running" exit-0 path (which empirically fails the preview pane).fix(desktop): clear stale
versionPendingInstallon boot so the relaunch toast doesn't reappear for the version you're already runningThe auto-updater writes
AppState.versionPendingInstallwhen an update finishes downloading and clears it in exactly one place — theok:update:relaunch-nowIPC handler invoked by Toast A's "Relaunch" button. The other install path (autoInstallOnAppQuit = true, which applies the staged update when the user quits the app normally) never touched the field, so the next launch left the app running on the new version with a stale pointer to it.main/index.ts'sbrowser-window-createdre-broadcast then surfaced that stale value as a phantom "Version X ready to install" toast for the version the app was already running.The boot path now compares
app.getVersion()againstversionPendingInstallvia a smallversionAtLeastMMP helper; when the running version has caught up, the field is cleared and the persisted state advances. Malformed inputs fall through to "don't clear" so a parse failure can't drop a genuinely-pending update on garbage.Fix Open Knowledge.app failing to open projects on macOS 26.4.x (Tahoe).
On macOS 26.4.x, Apple tightened AMFI enforcement of restricted entitlements on Electron helper processes. Helper binaries (Renderer / GPU / Plugin /
utilityProcess.forkchildren) do not carry an embedded provisioning profile — only the main app does. With the prior config sharing one entitlements file across both, helpers claimedcom.apple.developer.associated-domains(profile-restricted) without a profile and macOS SIGKILLed them at launch. Parent process sawexit_code=9. Symptom on every project open:Unable to open project — utility exited before ready (code=9)
…plus repeated
GPU process exited unexpectedly: exit_code=9and eventuallyFATAL: GPU process isn't usable. Goodbye.in stderr. Sibling Electron 41.2.1 apps (Granola, Linear, Notion) were unaffected because their helper plists already followed the standard Electron split.This patch splits the entitlements per Electron's canonical pattern:
packages/desktop/build/entitlements.mac.plist(unchanged) — main-app entitlements (restrictedassociated-domains+ file-access).packages/desktop/build/entitlements.mac.inherit.plist(new) — helper- only: JIT trio +cs.allow-dyld-environment-variables+inherit.packages/desktop/electron-builder.ymlmac.entitlementsInheritnow points at the new file.
A contract test (
tests/unit/entitlements-helper-split.test.ts) pins forbidden-keys exclusion + required-keys inclusion + allowlist + symmetric YAML wiring so the shape can't drift again.Users on the prior installed build (0.5.0-beta.31 or earlier) on macOS 26.4.x will pick up the fix in the next signed beta DMG. The installed package cannot be patched in place — entitlements are part of the code signature.
fix(open-knowledge): mdast→PM dispatcher no longer corrupts documents to literal type-name text when a schema is missing an extension.
The mdast→PM dispatcher's
inlineUnknownHandlerfallback emittednode.type(an internal mdast identifier — e.g."footnoteReference") as a plain text node whenever it ran without a matching schema entry. The emitted text concatenated against neighbouring prose, turningHello[^1].intoHellofootnoteReference.on disk — indistinguishable from authored content, and re-saved verbatim by every subsequent serialize pass.Defense lives at three layers:
inlineUnknownHandlernever emits a bare mdast type name as text. Whennode.valueis present (text-shaped inline mdast likeinlineCode), use it. Otherwise emit«unknown:${node.type}»— characters bracketed by«…»so the failure mode is visually auditable instead of looking like prose. Any future unhandled inline type degrades to a recognisable sentinel rather than silent corruption.footnoteReferenceschema-drift fallback recovers the markdown source. When theFootnoteReferencePM extension is missing from the schema, the dispatcher now reconstructs the[^${identifier}]source form as text so the bytes round-trip byte-stable. A drifted surface produces a document that re-parses correctly on the next non-drift surface instead of irreversibly losing the reference.footnoteDefinitionschema-drift fallback surfaces a visible marker. Pre-fix the library's built-in ignore list silently dropped the entirefootnoteDefinitionnode when the matching PM extension was absent — the user lost the body and the marker with no warning. The dispatcher now emits a[^${identifier}]:paragraph so the user can see the definition existed and re-author the body. (Body content is intentionally lossy under drift — children are arbitrary blocks and a faithful reconstruction would re-enter the broken dispatch; surfacing the marker beats silent drop.)
Adds five regression tests in
footnote.precision.test.tscovering the drift surface explicitly: stripped extensions, reference-without-corruption, byte-stable round-trip, definition marker visibility, and a class-level guard that the bare mdast type name never appears as text content for any input.Fix client-side schema validation failures on
set_folder_rule,discover, and six sibling MCP tools.Strict-validation MCP clients (Claude Code, Claude Desktop) rejected every call to
set_folder_rule,discover,write_template,delete_template,get_config,get_components,grep, andsearchwithStructured content does not match the tool's output schema: data must NOT have additional properties. Root cause:textPlusStructuredauto-injects a_textfield intostructuredContentas a client-quirk workaround (Claude Code hidescontent[]whenstructuredContentis present and the visible body is mirrored under_text), but no tool'soutputSchemadeclared_text, so the SDK-emitted JSON-schema (additionalProperties: false) rejected it under AJV.Fix: single-source
outputSchemaWithText(shape)helper that lays down_text: z.string().optional()alongside the caller's shape. Applied to all eight tools that combineregisterToolwithtextPlusStructured. Caller can override_text(mirrors the runtime escape hatch intextPlusStructured).Fix MCP tool input-validation errors to name the missing field and list allowed values (PRD-6659). Calling
write_document(or any other MCP tool with a required enum/string argument) without the required argument used to surface as a bareRequired— or, on Zod v4, a multi-line JSON dump of the issues array — with no obvious way for an agent to see which field was missing or what values were valid. A newinstallPrettyZodErrorspatch onMcpServerre-formats validation failures withz.prettifyError, so a missingpositionnow renders as✖ Invalid option: expected one of "append"|"prepend"|"replace" → at position. Applies uniformly across every tool with a Zod input schema; saves one round-trip on every agent's first tool call.fix(cli/mcp): detect bundle drag-replace from inside
ok mcpchildren and shut down cleanly so external hosts respawnWhen a user drag-replaces
/Applications/Open Knowledge.appto install a new DMG, any external MCP host process (Claude Desktop, Cursor, Codex, Windsurf, VS Code, …) that has already spawnedok mcpchildren keeps those children running off the previous bundle's executable mappings until the children are explicitly killed (or until each host happens to restart its own MCP transport). The host keeps talking to the previous version's code path;ok --versionin a terminal can report the new version while MCP-mediated edits still run on the old version. There is no user-visible signal that this divergence exists.This is the cli-side sibling of the desktop-side drag-replace fix — see
bundle-replace-detector.tsfor the OK desktop app's About-dialog /NSBundle.mainBundle-cache equivalent. The two fixes close the same root cause (drag-replace mutates the inode while live processes hold the previous one open) from opposite ends.The cli now exposes a darwin-gated
startBundleIdentityWatcher(5-minsetInterval, edge-triggered, single-shot disarm) wired in tostartGlobalMcpServer. Boot-time identity is captured viafileURLToPath(import.meta.url)— notprocess.execPath, which is Bun's binary outside the bundle — and the watcher periodicallystats the captured path. When the inode changes vs the captured value, the child logs a structuredbundle-movedbreadcrumb and triggers idempotentshutdown()with a 5-secondunref()'d deadline. The host's transport sees EOF, recycles, and respawns a fresh child off the new bundle on its next call.Detection is single-shot per session (re-arming would prompt twice when the user drag-replaces over an already-replaced binary); idempotent shutdown (the deadline kills the process even if the MCP server's own close path stalls). No behavior change on non-darwin platforms — Windows/Linux desktop parity is deferred per
packages/desktop/README.md.fix(open-knowledge): serve SPA-bundle fonts/sprites/PNGs from
dist/underok uiinstead of 404'ing through the content-asset middleware.ok uiruns the content-asset middleware before the SPA static handler. The middleware's fail-closed branch refuses to fall through for any path whose extension is inASSET_EXTENSIONS(woff/woff2/png/svg/jpg/…) when the file is absent under contentDir — so every Vite-hashed asset Vite emits at/assets/<hash>.<ext>got intercepted before sirv could serve it frompackages/cli/dist/public/assets/. Fonts silently fell back toui-sans-serif/system-ui; bundled icon SVGs and PNGs also 404'd.ui.tsnow routes/assets/<file>to the static handler first. If sirv misses (the SPA bundle doesn't ship that file), the request falls through to the content-asset middleware so user uploads under<contentDir>/assets/foo.pngstill serve with the same Content-Disposition policy and fail-closed 404 guard as before. The STOP-rule fail-closed behavior is preserved for asset paths outside/assets/and for/assets/<missing-file>(sirv miss → middleware miss → 404, not SPA shell).Electron +
ok start+ the Vite dev plugin are unaffected: they don't go throughui.ts's dispatcher, and the renderer in Electron loads SPA assets viafile://, not HTTP.Two regression tests in
packages/cli/src/commands/ui.test.ts:- a woff2 from
packages/app/dist/assets/serves 200 with noContent-Disposition(proving the static handler responded) - a user-staged PNG at
<contentDir>/assets/user-upload.pngstill serves 200 withContent-Disposition: inline+X-Content-Type- Options: nosniff(proving the content middleware fall-through fires)
- a woff2 from
fix(desktop): deliver post-update Toast B on auto-update relaunch
The "Updated to Version X" sidebar notice silently dropped on every auto-update relaunch. State.json confirmed the boot logic ran correctly (
lastSeenVersionadvanced,persistSafelysucceeded), but the IPC never reached the renderer.Root cause is a race in
whenRendererReady'stryFirehelper inpackages/desktop/src/main/index.ts. On boot,bootAutoUpdaterruns beforewm.createProjectWindowhas constructed the editor window (the project-open path is fire-and-forget with several awaits before the window is created), sowhenRendererReadyregistersapp.once('browser-window-created', tryFire). Electron emits that event synchronously insidenew BrowserWindow(opts), four lines beforeawait window.loadURL(...)runs. At that instantwebContents.isLoading()returnsfalsebecause no load has been initiated yet, sotryFireinterprets it as "already loaded" and fires the broadcast immediately — against a renderer whosemain.tsxhasn't run and whoseinstallUpdateNoticesBridge()subscriber isn't attached. Electron drops main→renderer IPC sent to an unloaded page.tryFirenow treatswebContents.getURL() === ''as "never navigated" alongsideisLoading(), so a fresh window emerging frombrowser-window-createdwaits fordid-finish-loadbefore the broadcast fires. Affects all three boot-routed toasts (A/B/C) routed throughwhenRendererReady, but Toast B was the only one that hit the race deterministically (A'supdate-downloadedarrives after a network roundtrip when the editor is loaded; C's stuck-hint fires after an initial-check grace period).fix(rename): preserve document body when renaming a file with the editor open
Renaming a file (sidebar or MCP) while an editor was connected to it could wipe the document body: the editor and every later reader saw an empty document even though the original content was intact on disk.
The managed-rename spine emitted the connection-close that forces connected clients to reconnect — and be redirected to the new name — before it had moved the file on disk. The redirected client's load ran against a not-yet-existent destination file, leaving an empty in-memory document that nothing re-populated once the move landed. The disk move now completes before the close, so the redirected client always loads the renamed file's real content.
fix(cli/auth): plug three token-leak paths in the share-link flow
Three independent bugs in the CLI's auth handling all surfaced from the share-link flow:
Cloning a share link to a public GitHub repo failed with "Repository not found" whenever the recipient's stored token was a fine-grained PAT or org-restricted token. The credential helper unconditionally injected that token, and GitHub returns 404 for any repo outside the token's scope. The clone command now probes the GitHub API anonymously first; on success, the credential helper is skipped entirely so the request goes through as a normal public read.
ok auth signoutcould leave a plaintext token at~/.ok/auth.yml. Storage selection (keyring vs file) is decided per-run by probing the@napi-rs/keyringnative binding, and the probe result can flip between sessions. When a token was written under the file fallback and signout ran later under the keyring backend, signout only cleared the empty keychain and never touched the file. Signout now clears every backend it knows about, regardless of which one resolves this run. It does not create an emptyauth.ymlfor keychain-only users.Users authenticated via the
ghCLI were still prompted for device-auth login when running the desktop app on macOS. macOS GUI launches inherit only thelaunchdPATH (/usr/bin:/bin:/usr/sbin:/sbin), soghinstalled under/opt/homebrew/binwas invisible to subprocess spawns and the auth cascade fell through to "no token".detectGhnow falls back to a list of known install locations (Homebrew Apple Silicon and Intel, MacPorts, Linux snap, distro packages) when the bare PATH lookup fails.
fix(desktop): derive the auto-update channel from the build version, not a runtime preference (fix-update-channel-from-build)
The desktop app's update channel is now sourced from
app.getVersion()—0.4.0-beta.36→ beta feed,0.4.0→ stable feed — so the feedelectron-updaterchecks (and therefore the in-app "release notification" / "What's New" notice) always matches the binary the user installed. Previously a persistedAppState.updateChannelcould diverge from the build (e.g. a beta install withupdateChannel: 'latest'), causing a beta build to surface stable-channel releases.Removed:
- the Settings → Update channel switcher (
ChannelSection) and theuseUpdateChannelsetter - the persisted
AppState.updateChannelkey — existingstate.jsonfiles carrying it are migrated by silently dropping it on first read - the
ok:update:set-channel/ok:update:confirm-downgradeIPC channels and theok:state:update-channel-changedevent, plus the beta→stable downgrade-warning notice (it only ever fired for runtime channel switches)
state.query()still reports the channel (now build-derived) so the BETA badge and About-panel label keep working.- the Settings → Update channel switcher (
Fix git identity writes from linked worktrees no longer silently mutating the main checkout.
POST /api/local-op/auth/set-identitynow detects when it's running in a linkedgit worktreeand writes per-checkout via--worktree(enablingextensions.worktreeConfigonce, idempotently); main checkouts retain the existing--localbehavior.resolveGitIdentityreads the per-worktree scope first, so each linked worktree can carry its own committer identity.ok initnow writes.claude/launch.json'sopen-knowledge-uientry as{runtimeExecutable: 'npx', runtimeArgs: ['-y', '@inkeep/open-knowledge@latest', 'ui']}instead of the bare 2-arg form.ok startsweeps the project's launch.json at boot and forward-migrates any pre-existing bare-npx entries to the new canonical shape.Closes the same silent-downgrade hole as the MCP-canonical pin: the bare-name form routed through npm's range-spec resolver could silently downgrade Claude Code Desktop's preview-pane spawn (
preview_start("open-knowledge-ui")) to a years-stale release on older Node versions. Pinning@latestmakes the resolver use the literallatestdist-tag and surfaces a Node-version mismatch as a loudEBADENGINE.The startup sweep only rewrites bare-managed entries. Version-pinned (
@beta,@x.y.z), dev-mode (node /path/cli.mjs ui), foreign-customized, and unrelated configurations co-located in the same file are preserved.ok initnow writes MCP host configs as{command: 'npx', args: ['-y', '@inkeep/open-knowledge@latest', 'mcp']}instead of the bare 2-arg form.ok startsweeps user-level and project-level configs at boot and forward-migrates any pre-existing bare-npx entries to the new canonical shape.The bare-name form (
['@inkeep/open-knowledge', 'mcp']) routes through npm's range-spec resolver, which sorts by engine compatibility and can silently downgrade users on older Node versions to years-stale releases. Pinning@latestmakes the registry resolve the literallatestdist-tag and lets a Node-version mismatch surface as a loudEBADENGINEinstead.The startup sweep only rewrites bare-managed entries. Version-pinned (
@beta,@x.y.z), dev-mode (node /path/cli.mjs), desktop-bundled (/.../ok.sh), and custom-command entries are preserved.fix(open-knowledge): MCP tool responses no longer drop their visible body on Claude Code and Claude Desktop.
Claude Code and Claude Desktop hide the
contenttext stream of an MCP tool result wheneverstructuredContentis present — the agent sees only the structured payload and the visible body silently vanishes. The sharedtextPlusStructuredhelper used by 26 OK MCP tools (read_document,ingest,research,consolidate,search,grep, theget_*tools, every write/edit/rename/rollback handler, etc.) previously put only typed metadata (e.g.{ previewUrl: null }) intostructuredContent, so on those clientsread_document(...)returned{previewUrl: null}with the file body gone andingest/research/consolidatereturned{previewUrl: null}with the workflow prompt gone.The helper now mirrors the visible body under
structuredContent._text, so the body survives the client quirk for every caller of the helper in one place. The underscore prefix marks the field as internal / auto-generated (matching MCP spec's_metaconvention) so existing exact-match tests onstructuredContentshape don't trip on the extra key.exec.tsalready had its own per-tool duplication for the same reason (structuredContent.stdout); that field stays because it carries the raw command stdout without banners or enrichment — a semantically distinct slice, not a duplicate of the visible content.Same change also refactors the three workflow tools (
ingest,research,consolidate) to share a singlebuildWorkflowHandlerinstead of three near-identical per-fileregister()bodies, and hoists their byte-identical*Depsinterfaces into oneWorkflowToolDepstype. Net: 60+ lines of duplicated registration boilerplate collapse into one shared helper, and the{ previewUrl: null }workflow-tool contract is now encoded in one place.No behavior change on clients that respect both channels — they continue to see the same
contentarray plus the same structured fields, with one extra_textmirror.Fix collab WebSocket connection from
ok uiso the React shell can reach Hocuspocus from inside sandboxed agent preview panes (Claude Code, etc.).Before this change,
/api/configadvertised the collab WebSocket asws://localhost:<collab-port>/collab— a different port from whereok uiserved the React shell. The browser opened the WebSocket directly to the collab server. That works in a normal browser tab but fails in agent preview panes that sandbox the renderer to one URL/port (the editor pane shows "Connection dropped — Lost connection to '<doc>'" for every.md/.mdxfile).Now:
/api/configadvertises the WebSocket on the same origin the shell loaded from (derived from the request's Host header, with the existing loopback allowlist).ok uiregisters a/collabupgrade handler that forwards the WebSocket to the collab server's port (read fromserver.lock) via raw TCP. Connection / Upgrade / Sec-WebSocket-* headers are preserved verbatim so the upstream Hocuspocus can complete the handshake.
Result: the same URL serves shell + WebSocket. Works in regular browsers (unchanged behavior — same-origin always succeeds) AND in sandboxed preview panes (the new working case).
Forward WebSocket upgrades through
ok ui's proxy mode so the editor works in Claude Code's preview pane when OK Desktop is open for the same project.When OK Desktop holds
ui.lockfor a project and Claude Code spawnsok uifor the same project (via itslaunch.json),ok uifalls into proxy mode — binds the requested port (LAUNCH_JSON_PORT = 39848) and forwards HTTP to the Desktop utility's port. Before this change, the proxy forwarded HTTP fine (file tree rendered) but had nohttpServer.on('upgrade')listener, so the per-document WebSocket upgrade was dropped — Claude Code's preview pane showed "Connection dropped" for every.md/.mdxfile.The proxy now wires the same
proxyUpgradehelper thatok ui's main HTTP server uses. The upstream (Desktop utility) handles/collabupgrades natively via Hocuspocus. Live upgrade-pipe sockets are tracked soclose()can drain them on shutdown (httpServer.close()does not track upgrade-detached sockets).Closes the Desktop + Claude Code-on-the-same-project scenario, completing the WS-upgrade fix landed in the previous PR (which covered the direct-bind path but explicitly left proxy mode out of scope).
Sidebar context menus + Open with AI dispatch unification + macOS File/View menu refresh.
The Open Knowledge editor's file sidebar gets three coherent right-click menus (empty-space, folder, file) with consistent ordering, and the macOS application menu picks up the same affordances in keyboard-reachable form. The Open with AI handoff (previously file-row-only) now dispatches at file / folder / project scope from every surface — right-click menus, the sparkle icon in the editor header, and the new File menu items.
- Three sidebar right-click menus. Empty-space (new surface — right-click anywhere in the sidebar, not just on rows), folder, and file menus share a five-section ordering: create → act → filter → tree state → destructive. Net additions include Open in Terminal at every scope, Show Hidden Files (
Cmd+Shift+.) / Show all files project-local visibility toggles, and Open with AI on folder rows + empty space (previously file-only). - Open with AI dispatches at every scope. Right-click menus, the editor-header sparkle icon, and the macOS File menu all use the new label. The dispatch payload is a short text prompt asking the agent to open the target in Open Knowledge's web preview — replacing the prior file-attachment / cwd-set semantics. Three prompt templates: file-scope (
Can you open <path> in web view with open knowledge editor.), folder-scope (Let's work on `<folder>` folder using Open Knowledge. Open the OK editor in web view.), project-scope (Let's work on this project using Open Knowledge. Open the OK editor in web view.). Cursor's two-step spawn, Claude's claude.ai web fallback (file scope only), and per-agent install detection are all preserved. - Sparkle icon dispatches at 3 scopes. The editor-header sparkle picks the dispatch scope from the active editor target — file when a doc is open, folder when a folder overview is the active view, project when nothing is active. Previously the sparkle only rendered when a doc was open.
- Delete now uses the system Trash. Recoverable from Finder Trash, with a VS Code-parity confirm modal (
Are you sure you want to delete '<name>'?/Move to Trash/Cancel) and a secondary fallback modal (Couldn't move to Trash/<target context> Do you want to permanently delete instead?/[Cancel][Retry][Delete Permanently]) whenshell.trashItemfails (locked file, network drive without Trash, OneDrive quirks). Multi-target failures aggregate into one modal listing every failed path. Replaces today's hardunlinkSync/rmSyncfor UI-driven deletes; agent and non-UI HTTP callers continue to use the hard-delete path. - Open in Terminal. New menu item across all three sidebar menus and the macOS File menu. Spawns
open -a Terminal.app <dir>(argv array,shell: false, 2 s timeout, project-containment check). Targets are scope-appropriate: folder → folder, file → parent dir, empty-space → project root, asset row → parent dir. Terminal.app is hardcoded for v1; user-configurable terminal preference is on the roadmap. - Show Hidden Files / Show all files. Two project-local visibility toggles persisted in
.ok/local/config.ymlunderappearance.sidebar.{showHiddenFiles, showAllFiles}. Show Hidden Files (bound toCmd+Shift+.matching Finder convention) bypasses the client's dot-segment drop (recovers allowed dotfiles likebrain/.archived/note.md); server filters still apply. Show all files bypasses every filter — client AND server — and surfaces every file on disk under the content directory except synthetic system docs and a curatedBUILTIN_SKIP_DIRSset (.git,.ok,node_modules, framework caches, build output, OS dirs). Mirrored as checkbox items in the macOS View menu so flipping either surface keeps the sidebar and menu bar in sync via the existing CRDT subscription. - macOS File menu gets a state-aware item-management section below the project navigation items: New File (
Cmd+N), New Folder (Cmd+Shift+N), New from Template…, Rename, Move to Trash (Cmd+Delete), Reveal in Finder, Open in Terminal, Open with AI ▸, Copy Path ▸. Enable / disable per the active editor target. Hide-this-file / Hide-folder stay sidebar-only. - macOS View menu gets Show Hidden Files (
Cmd+Shift+.), Show All Files, Expand All, Collapse All before the Zoom and Fullscreen items. - Accelerator rebinds.
Cmd+N= New File (added).Cmd+Shift+N= New Folder (was Switch Project…).Cmd+Delete= Move to Trash (matches Finder / VS Code).Cmd+Shift+P= Switch Project… (new home for the rebound accelerator).Cmd+Shift+.= Show Hidden Files (matches Finder).Cmd+O(Open Folder) andCmd+,(Settings…) unchanged.
1-way door —
Cmd+Shift+Nmuscle memory. Existing users seeCmd+Shift+Ninvoke New Folder instead of Switch Project…; Switch Project is nowCmd+Shift+P. Aligns with macOS HIG defaults, Finder, and VS Code.Install-state detection — capability-tier. Open with AI install detection (
useInstalledAgents()+GET /api/installed-agents) now applies the per-scheme OS probe only when the browser and server share a host (localhost/127.0.0.1/[::1]). On remote-hosted setups (the server's machine differs from the user's browser machine), every agent renders and the browser's OS protocol-dispatch dialog ("Open Cursor?") becomes the truth signal — no silent failure from probing the wrong filesystem.New IPC channels.
ok:shell:trash-item(Trash viashell.trashItemwith reason-union failure shape),ok:shell:open-in-terminal(Terminal.app spawn with argv allowlist + project containment),ok:editor:active-target-changed(renderer → main, drives File menu enable / disable),ok:sidebar:expand-all/ok:sidebar:collapse-all(main → renderer, View menu fan-out). Web mode hides the shell-IPC items entirely (noshell.trashItem/ process-spawn in browsers).New HTTP endpoint.
POST /api/trash/cleanup { kind, path }— runscaptureAndCloseDocuments+recentlyRemovedDocs.setDeleted+ index update + CC1 emit after the renderer's IPC trash step succeeds. PreservesextractActorIdentityattribution at the new entry point.New config fields.
appearance.sidebar.showHiddenFilesandappearance.sidebar.showAllFiles(bothboolean, defaultfalse, project-local scope).Legacy directory cleanup. Dropped backwards-compat scanner entries for the pre-rename
.open-knowledge/.openknowledgeper-project state dirs (greenfield — no users to preserve compat for). Both legacy names added toBUILTIN_SKIP_DIRSso any disk residue from older OK installs stays out of the sidebar.Spec:
specs/2026-05-16-sidebar-context-menus/SPEC.md.- Three sidebar right-click menus. Empty-space (new surface — right-click anywhere in the sidebar, not just on rows), folder, and file menus share a five-section ordering: create → act → filter → tree state → destructive. Net additions include Open in Terminal at every scope, Show Hidden Files (
Tighten the bundled Open Knowledge Agent Skill: eleven prose edits to
packages/server/assets/skills/open-knowledge/SKILL.mdthat close cross-round agent-feedback gaps without changing any MCP behavior.- New
## TL;DR — the 90% casesection between the skill-version note and the STOP block names the four daily-driver moves (Reads / Writes / Preview / Workflow tools) so agents make a correct firstwrite_documentcall after reading <300 words instead of internalizing the whole skill. - Promote the "workflow tools return procedural guidance, not data" framing from a buried trailing line into the section opener — closes the naming-expectation gap where
ingest("url")reads as "do the ingest" but returns a multi-step plan. - New frontmatter-editing paragraph explains that
edit_documentrejects frontmatter-intersecting patches with HTTP 400 and routes agents towrite_document({ position: "replace" }). Matching row added to the anti-pattern table. - Stale-MCP failure-mode signpost names the "Text not found on text that demonstrably exists" symptom and routes agents to the
Open Knowledge MCP unavailable:escape hatch instead of silent retry loops. - Per-tool autonomy gates (research scoping, consolidate decision-confirmation) clarified to NOT be overridden by session-level "work without stopping" hints.
- Workflow-tools 2nd+ invocation skim guidance — agents can skim repeat instructional bodies in-session without re-internalizing.
ls -larecommended over plainlsfor directory listings — OK projects carry dot-prefixed entries (.ok/, nested.ok/,.okignore) that plainlsomits.- User-scope template noise — skip
scope: "user"entries whose title/description don't match the folder's purpose (e.g.daily-journalinwiki/). ok seedversion-floor caveat appended to the skill-version note — heads off the first-time-user wall when an older@inkeep/open-knowledge(< 0.4.0) is globally installed.- Seed-pack wiki-link legacy note — seed-pack templates may still emit
[[Page]]placeholders; agents are told to replace them with standard markdown links during the{shape}-fill pass. - Description-line cleanup — the trailing "Authoritative — MCP server instructions overlap…" sentence (agent-routing meta-commentary that was surfacing in Cowork's permission-preview chip) is dropped from the frontmatter
descriptionand relocated to a body callout under the H1 tagline.
Spec:
specs/2026-05-15-ok-skill-content-cleanup/SPEC.md. No MCP, CLI, or server-runtime changes — the only artifact that changed is the bundled SKILL.md.- New
fix(config): rename user-global config from
~/.ok/config.ymlto~/.ok/global.ymlThe user-global config now lives at
~/.ok/global.yml. The project marker stays.ok/config.yml, so the two are no longer name-twins — the ancestor-walk that detects an OK project can never treat the user's home directory as a project root, and users editing one file can't confuse it with the other.The CRDT doc name
__user__/config.ymlis intentionally unchanged — it's an opaque internal identifier and renaming it would be a wire-level change for connected clients.
0.5.0-beta.5
Patch Changes
fix(desktop): clear stale
versionPendingInstallon boot so the relaunch toast doesn't reappear for the version you're already runningThe auto-updater writes
AppState.versionPendingInstallwhen an update finishes downloading and clears it in exactly one place — theok:update:relaunch-nowIPC handler invoked by Toast A's "Relaunch" button. The other install path (autoInstallOnAppQuit = true, which applies the staged update when the user quits the app normally) never touched the field, so the next launch left the app running on the new version with a stale pointer to it.main/index.ts'sbrowser-window-createdre-broadcast then surfaced that stale value as a phantom "Version X ready to install" toast for the version the app was already running.The boot path now compares
app.getVersion()againstversionPendingInstallvia a smallversionAtLeastMMP helper; when the running version has caught up, the field is cleared and the persisted state advances. Malformed inputs fall through to "don't clear" so a parse failure can't drop a genuinely-pending update on garbage.
0.5.0-beta.4
0.5.0-beta.3
Minor Changes
BREAKING: Mermaid diagrams are now authored exclusively as
```mermaidfenced code blocks. Existing<Mermaid chart="…" />JSX must be manually converted to a mermaid fence — legacy JSX will display as an editable source block instead of a rendered diagram.To migrate: search your content for
<Mermaid chart=and rewrite each occurrence as a```mermaidfence. Untouched legacy content remains visible and editable as an unknown-component source block, but no longer renders as a diagram until converted.Mechanism: the canonical descriptor was renamed
Mermaid→MermaidFenceso legacy<Mermaid />JSX no longer matches at parse time and falls through to the unknown-component editable source block (no silent migration on save). Theidandthemeprops were dropped (neither is expressible in fence syntax). Slash menu and PropPanel continue to show "Mermaid" viadisplayName.
0.5.0-beta.2
Minor Changes
Project creation under a directory whose parent contains
.git/now opens the parent (the git working-tree root) AND defaults the content scope to that same parent — opened folder andcontent.diralign by default. Previously the parent was opened butcontent.dirwas silently scoped to the picked sub-folder, so the user landed in the git root visually while writes were narrowed beneath it.Surfaces the WHY in three places:
- CLI (
ok init):[ok] Initialized OK at <gitRoot> — opened parent of <cwd> because it contains a .git folder(was(scoped to <subPath>/)). - Desktop toast on freshly-spawned editor windows:
Initialized OK at <gitRoot> — opened parent of <pickedPath> because it contains a .git folder(toast IPC payload'scontentDirfield replaced withpickedPath). - Desktop consent dialog banner: explicit "parent of
<pickedPath>because it contains a.gitfolder (one .ok/ per git repo). The default scope is the entire repo; change Content directory below to narrow it."
Narrowing back to the originally-picked sub-folder is still a one-edit choice — the consent dialog's Content directory field is pre-filled with
.and accepts any sub-path, andconfig.yml'scontent.dirremains the post-init knob.- CLI (
0.4.1-beta.1
Patch Changes
fix(desktop): derive the auto-update channel from the build version, not a runtime preference (fix-update-channel-from-build)
The desktop app's update channel is now sourced from
app.getVersion()—0.4.0-beta.36→ beta feed,0.4.0→ stable feed — so the feedelectron-updaterchecks (and therefore the in-app "release notification" / "What's New" notice) always matches the binary the user installed. Previously a persistedAppState.updateChannelcould diverge from the build (e.g. a beta install withupdateChannel: 'latest'), causing a beta build to surface stable-channel releases.Removed:
- the Settings → Update channel switcher (
ChannelSection) and theuseUpdateChannelsetter - the persisted
AppState.updateChannelkey — existingstate.jsonfiles carrying it are migrated by silently dropping it on first read - the
ok:update:set-channel/ok:update:confirm-downgradeIPC channels and theok:state:update-channel-changedevent, plus the beta→stable downgrade-warning notice (it only ever fired for runtime channel switches)
state.query()still reports the channel (now build-derived) so the BETA badge and About-panel label keep working.- the Settings → Update channel switcher (
0.4.1-beta.0
Patch Changes
fix(config): rename user-global config from
~/.ok/config.ymlto~/.ok/global.ymlThe user-global config now lives at
~/.ok/global.yml. The project marker stays.ok/config.yml, so the two are no longer name-twins — the ancestor-walk that detects an OK project can never treat the user's home directory as a project root, and users editing one file can't confuse it with the other.The CRDT doc name
__user__/config.ymlis intentionally unchanged — it's an opaque internal identifier and renaming it would be a wire-level change for connected clients.