diff --git a/.changeset/bundle-parcel-watcher-into-cli.md b/.changeset/bundle-parcel-watcher-into-cli.md new file mode 100644 index 000000000..f13907748 --- /dev/null +++ b/.changeset/bundle-parcel-watcher-into-cli.md @@ -0,0 +1,5 @@ +--- +"@inkeep/open-knowledge": patch +--- + +Restored the native `@parcel/watcher` file watcher in the packaged desktop app (inkeep/open-knowledge#760). The app runs `ok start` from a bundled CLI whose module resolver never reaches the app's own `@parcel/watcher` copy, so at server boot `import('@parcel/watcher')` failed with "Cannot find package" and the watcher silently fell back to chokidar. The macOS build now stages `@parcel/watcher` — its wrapper, the runtime dependencies it loads (`picomatch`, `is-glob`, `is-extglob`, `detect-libc`), and the per-architecture native binary — onto the CLI's own module path, so the fast native watcher loads as intended. Combined with the chokidar-fallback fix in the same release, external edits to files in subfolders are reflected in the graph, backlinks, and dead-link views without a restart. The staging resolves each dependency exactly as the wrapper loads it (the workspace carries two `picomatch` majors, so a naive copy could have shipped the wrong one), and a packaging test verifies the staged tree is CLI-resolvable without needing a full app build. diff --git a/.changeset/link-suggest-by-name.md b/.changeset/link-suggest-by-name.md new file mode 100644 index 000000000..6bf400b90 --- /dev/null +++ b/.changeset/link-suggest-by-name.md @@ -0,0 +1,7 @@ +--- +"@inkeep/open-knowledge": patch +--- + +Link-target suggestions now match by name as you type, like the command palette. + +When editing a link target (the "Edit markdown link" dialog, the wiki-link panel, and the bubble-menu link popover all share the same field), typing a bare page name such as `install` now opens the suggestion dropdown and surfaces the matching page — a leading `/` is no longer required. Typing or pasting an external URL, or a bare `#anchor`, still does not pop a page list. The empty/browse list also keeps real content pages ahead of dot-directory files (`.github/`, `.changeset/`) so they no longer fill the list on projects rooted at a repository. diff --git a/.changeset/pull-only-sync-mode.md b/.changeset/pull-only-sync-mode.md new file mode 100644 index 000000000..7effab3a7 --- /dev/null +++ b/.changeset/pull-only-sync-mode.md @@ -0,0 +1,22 @@ +--- +"@inkeep/open-knowledge": minor +--- + +Add Follow — a one-directional sync mode for people who follow a knowledge base they can read but not push to (share-link recipients and read-only followers). + +Previously, opening a repository you lacked push access to left your copy silently frozen: the sync onboarding prompt was suppressed, no sync config was written, and the engine parked itself the moment a push was denied. Follow fixes that dead end. + +What it does: + +- Keeps your local copy current automatically by fast-forwarding to the latest on GitHub, without ever pushing or committing on your behalf. +- Lets your own uncommitted edits ride safely on top of incoming updates. Non-overlapping changes combine automatically; edits to the same lines the upstream changed surface in the familiar conflict view, where you choose keep-mine or take-theirs. Your branch always tracks the upstream tip and never forks. +- Is always opt-in. Push-denied users are now offered Follow at first open with plain-language copy ("updates flow in from GitHub; your edits stay on this computer"), and anyone can choose Off / Follow / Full in Settings. A project owner can commit `autoSync.default: follow` so a fresh clone starts following. + +Also included: + +- Full-sync users who lose push access now get a "Switch to Follow" action on the sync-paused notice instead of a silent stall; switching keeps any unshared local commits as a local overlay. +- The sync status badge stays visible for a followed project and shows a distinct following state (updating / up to date / conflict / offline). +- Anonymous followers of public repositories poll more gently than signed-in followers. +- A one-shot "Sync" pull that reports its outcome, for surfaces that need an on-demand refresh. + +Configuration note: the sync preference is now stored as a single `autoSync.mode` (`off` / `follow` / `full`). Existing `autoSync.enabled` settings keep working, and choosing a mode clears the legacy flag so the two can't disagree across versions. An app that predates `autoSync.mode` then reads the config as unanswered and re-prompts rather than acting on a stale toggle, so it never pushes for a mode you didn't consent to there. diff --git a/.github/workflows/desktop-build-win-linux.yml b/.github/workflows/desktop-build-win-linux.yml index 8c0c3f390..2690561ee 100644 --- a/.github/workflows/desktop-build-win-linux.yml +++ b/.github/workflows/desktop-build-win-linux.yml @@ -193,6 +193,17 @@ jobs: working-directory: packages/desktop run: node scripts/prepare-platform-natives.mjs + # Stage @parcel/watcher onto the bundled CLI's module path. This workflow + # invokes electron-builder directly (not the build:win/build:linux npm + # scripts, which DO run this), so the step must be explicit here or the + # packaged app ships without @parcel/watcher on the CLI path and the + # server degrades to chokidar (#760). Stages only the host-arch binary + # today; the other-arch artifact uses the (subfolder-correct) chokidar + # fallback — see scripts/stage-parcel-watcher.mjs. + - name: Stage @parcel/watcher for the bundled CLI + working-directory: packages/desktop + run: node scripts/stage-parcel-watcher.mjs + - name: Build desktop main/preload/renderer working-directory: packages/desktop run: pnpm run build:desktop @@ -277,6 +288,17 @@ jobs: working-directory: packages/desktop run: node scripts/prepare-platform-natives.mjs + # Stage @parcel/watcher onto the bundled CLI's module path. This workflow + # invokes electron-builder directly (not the build:win/build:linux npm + # scripts, which DO run this), so the step must be explicit here or the + # packaged app ships without @parcel/watcher on the CLI path and the + # server degrades to chokidar (#760). Stages only the host-arch binary + # today; the other-arch artifact uses the (subfolder-correct) chokidar + # fallback — see scripts/stage-parcel-watcher.mjs. + - name: Stage @parcel/watcher for the bundled CLI + working-directory: packages/desktop + run: node scripts/stage-parcel-watcher.mjs + - name: Build desktop main/preload/renderer working-directory: packages/desktop run: pnpm run build:desktop diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 5744602ab..1e78aa5f1 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -222,6 +222,15 @@ jobs: working-directory: packages/desktop run: node scripts/prepare-universal.mjs + # Stage @parcel/watcher onto the bundled CLI's module path. This workflow + # invokes electron-builder directly (not the build:mac npm script, which + # DOES run this), so the step must be explicit here or the smoke/QA DMG + # ships without @parcel/watcher on the CLI path and the server degrades to + # chokidar (#760). + - name: Stage @parcel/watcher for the bundled CLI + working-directory: packages/desktop + run: node scripts/stage-parcel-watcher.mjs + # Step B: package via electron-builder. Kept as a separate runner step # from step A (rather than chained via && in a single shell). The split # was originally forced by a packaging bug in the previous script runner: diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index 80119b61d..5498ae8d6 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -341,6 +341,15 @@ jobs: working-directory: packages/desktop run: node scripts/prepare-universal.mjs + # Stage @parcel/watcher onto the bundled CLI's module path. This workflow + # invokes electron-builder directly (not the build:mac npm script, which + # DOES run this), so the step must be explicit here or the published DMG + # ships without @parcel/watcher on the CLI path and the server degrades to + # chokidar (#760). + - name: Stage @parcel/watcher for the bundled CLI + working-directory: packages/desktop + run: node scripts/stage-parcel-watcher.mjs + - name: Build + sign + notarize + publish DMG/ZIP # electron-builder picks up CSC_LINK / CSC_KEY_PASSWORD from env + # signs. afterSign.mjs picks up APPLE_* + notarizes + staples + diff --git a/docs/content/features/github-sync.mdx b/docs/content/features/github-sync.mdx index 254ba3c1f..09f3648d6 100644 --- a/docs/content/features/github-sync.mdx +++ b/docs/content/features/github-sync.mdx @@ -3,10 +3,18 @@ title: GitHub sync icon: custom/GitHub description: Clone repos from GitHub, auto-sync changes with your team, and resolve conflicts. --- -OpenKnowledge can connect a project to a GitHub remote repository and keep it synced over time. When GitHub sync is enabled, OpenKnowledge actively pulls commits from the remote and pushes commits back. + +import { Cloud } from 'lucide-react'; + +OpenKnowledge can connect a project to a GitHub remote repository and keep it synced over time. When GitHub sync is enabled, OpenKnowledge keeps your copy current with the remote. + +There are two modes for GitHub sync: + +- **Full** pulls commits from the remote and pushes your commits back. +- **Follow** fetches updates from the remote and never pushes or commits on your behalf. - Only enable GitHub sync for repositories where you are comfortable with OpenKnowledge writing commits to the remote history. If you are worried about automated commits cluttering your Git history, do not enable sync for that repository. + Only enable full GitHub sync for repositories where you are comfortable with OpenKnowledge writing commits to the remote history. If you are worried about automated commits cluttering your Git history, do not enable full sync for that repository. ## What GitHub sync is for @@ -27,7 +35,7 @@ These steps use the desktop app; from a terminal, [`ok clone`](/docs/reference/c ### Open the clone dialog - From the Navigator window, click **Clone from GitHub**. You can open the navigator window from the editor by clicking on your project name in the bottom left and then clicking **Switch project**. + From the Navigator window, click **Clone from GitHub**. You can open the Navigator window from the editor by clicking on your project name in the bottom left and then clicking **Switch project**. @@ -57,19 +65,30 @@ This will open a dialog where you can choose an owner and repository name. You c ## Enable GitHub sync -When you open a freshly cloned project, you will be prompted to enable sync. You can also toggle it from the project settings page at any time. +When you open a freshly cloned project, you will be prompted to enable sync. If you have full access to the repository, you will be prompted to enable **Full** sync. If you have read only access, you will be prompted to enable **Follow** sync. You can change the sync mode from the sync popover (the icon) or from **Settings → Sync** at any time. + +If you want to set a default sync setting for future collaborators, go to the project settings page and navigate to the **Sync** → **Shared default** section. This writes [`autoSync.default`](/docs/reference/configuration) — one of `off`, `follow`, or `full` — to the project's committed `.ok/config.yml`, pre-answering the enable-sync prompt for everyone who clones the repo; each machine's own auto-sync choice overrides it. -While sync is active, OpenKnowledge: +### Full + +With full sync active, OpenKnowledge: - Fetches commits from the remote - Commits your edits locally using the configured Git identity - Pushes those commits back to the remote so collaborators see your changes -If your Git identity isn't set, sync still commits under a default "OpenKnowledge" author; the sync status indicator reminds you to set one so teammates see your name. +If your Git identity isn't set, OpenKnowledge commits under a default "OpenKnowledge" author; the sync status indicator reminds you to set one so teammates see your name. Pulls can overwrite uncommitted local file changes, so commit or discard work-in-progress before you enable sync. -If you want to set a default auto-sync setting for future collaborators, go to the project settings page and navigate to the **Sync** -> **Shared default** section. This writes [`autoSync.default`](/docs/reference/configuration) to the project's committed `.ok/config.yml`, pre-answering the enable-sync prompt for everyone who clones the repo; each machine's own auto-sync choice overrides it. +### Follow + +With follow sync active, OpenKnowledge: + +- Fetches commits from the remote +- Never commits or pushes your edits to the remote + +Your own edits stay on this computer. You can keep editing a followed project: when an update arrives, non-overlapping changes combine automatically, and an edit to the same lines the remote changed surfaces in the [conflict view](#resolving-a-conflict) where you choose which side to keep. Your branch always tracks the remote tip and never forks. ## Authentication @@ -104,7 +123,7 @@ A few situations stop sync from completing successfully. In each case the sync s ### Conflicts and pending changes - **Unresolved conflicts.** If even one file has a conflict between your edits and your team's, sync waits. You can keep working on every other doc; only the conflicted files are frozen until you choose a side from the diff view. See [Resolving a conflict](#resolving-a-conflict) above. -- **Local changes would be overwritten by an incoming update.** A teammate's update is ready to pull, but a file it touches has uncommitted local changes. Doc edits are committed automatically before merging, so this usually means other tracked files, like configs. Sync pauses rather than clobber your work; commit, stash, or discard those changes from a terminal and sync resumes. +- **Local changes would be overwritten by an incoming update.** A teammate's update is ready to pull, but a file it touches has uncommitted local changes. Doc edits are committed automatically before merging, so this usually means other tracked files, like configs. Sync pauses rather than clobbering your work; commit, stash, or discard those changes from a terminal and sync resumes. - **Edits made outside the app aren't ready yet.** If you (or another tool) edited a file directly on disk while sync wanted to merge, OpenKnowledge waits until those external edits settle before merging. Usually this clears in a second or two on its own. ### Project isn't on a normal branch @@ -115,7 +134,7 @@ A few situations stop sync from completing successfully. In each case the sync s ### GitHub-side blockers - **Authentication errors.** Your stored credentials expired, were revoked, or don't have access to this repository anymore. Reconnect from the sync indicator (or from **Settings → Account**) to clear the error. -- **You don't have permission to push to this repo.** Someone shared a project backed by a GitHub repo where your account isn't a collaborator (or has read-only access on a private repo). OpenKnowledge checks this up front: instead of prompting you to enable sync that would just fail, the toggle is disabled with an inline "You don't have permission to push to this repo" message in both the sync indicator popover and **Settings → Sync**. If you had sync enabled on a different repo and switch to one you can't push to, OpenKnowledge pauses sync in-memory rather than mutating your preference; your `Auto-sync: on` setting is preserved for the next repo you have full access to. Ask the project owner for write access, then click sync to re-check. +- **You don't have permission to push to this repo.** Someone shared a project backed by a GitHub repo where your account isn't a collaborator (or has read-only access on a private repo). OpenKnowledge checks this up front and offers **Follow** instead of full sync. - **Protected branches and rejected pushes.** If the target branch requires reviews, signed commits, or status checks, GitHub rejects the push. OpenKnowledge turns sync off automatically so it stops retrying, and it stays off until you turn it back on from the sync indicator or **Settings → Sync**. Switch to a branch you can push to, or use a pull request for the protected branch instead. ### Temporary problems diff --git a/docs/content/features/share.mdx b/docs/content/features/share.mdx index 957df9262..a172ad524 100644 --- a/docs/content/features/share.mdx +++ b/docs/content/features/share.mdx @@ -66,6 +66,6 @@ Even on the right branch, a doc or folder can be renamed or deleted after a link ### Receiving without push permission -If the shared repo is one your GitHub account can't push to (you're not a collaborator on a public repo, or you have read-only access on a private one), OpenKnowledge skips the usual "Enable auto-sync?" prompt. Sync is also disabled for the same reason in **Settings → Sync**. If the project owner grants you write access later, push permission is re-checked the next time the project's server starts. See [GitHub sync: GitHub-side blockers](/docs/features/github-sync#github-side-blockers) for the full set of permission shapes the sync UI handles. +If the shared repo is one your GitHub account can't push to (you're not a collaborator on a public repo, or you have read-only access on a private one), OpenKnowledge offers **[Follow](/docs/features/github-sync#follow)** at first open instead of the full "Enable auto-sync?" prompt: your copy keeps up to date by pulling the latest from GitHub automatically, while your own edits stay on your computer. You can keep editing — an incoming update combines with your non-overlapping changes automatically, and an edit to the same lines surfaces as a conflict to resolve. See [GitHub sync](/docs/features/github-sync) for how to automatically sync changes to GitHub, and [Timeline and recovery](/docs/features/timeline-and-recovery) for per-doc history once the doc is open. diff --git a/docs/content/reference/configuration.mdx b/docs/content/reference/configuration.mdx index 90ede825c..ac31b5871 100644 --- a/docs/content/reference/configuration.mdx +++ b/docs/content/reference/configuration.mdx @@ -8,7 +8,7 @@ OpenKnowledge reads YAML config from three places: - **Project**: `./.ok/config.yml` (this project, committed to git) - **User**: `~/.ok/global.yml` (every project on your machine) -- **Project-local**: `./.ok/local/config.yml` (this project on this machine; gitignored). Holds per-machine-per-project preferences such as `autoSync.enabled`. Maintained by the editor (auto-sync onboarding modal, sync popover, Settings pane Sync section); you don't normally hand-edit it. +- **Project-local**: `./.ok/local/config.yml` (this project on this machine; gitignored). Holds per-machine-per-project preferences such as `autoSync.mode`. Maintained by the editor (auto-sync onboarding modal, sync popover, Settings pane Sync section); you don't normally hand-edit it. All files are optional; defaults cover everything. @@ -36,8 +36,9 @@ A key set in a file more specific than its scope (a user-scope key in `.ok/confi | `contentRules.plugins.markdownlint.enabled` | boolean | `false` | project | Enable the [markdownlint](/docs/advanced/content-rules/markdownlint) content-rules plugin for this project (shared via git). Off by default — turn it on in **Settings → This project → Plugins**. The rules themselves live in your native `.markdownlint.*` file, not here. | | `appearance.theme` | `"light" \| "dark" \| "system"` | (unset) | user | Editor theme. | | `appearance.sidebar.showHiddenFiles` | boolean | `false` | project-local | Show files whose path segments start with `.`. The sidebar otherwise lists every file on disk under the content directory; tooling internals (`.git/`, `.ok/`, `node_modules/`) stay hidden regardless. Toggled from the sidebar's right-click menu or the macOS **View → Show Hidden Files** item. | -| `autoSync.enabled` | `boolean \| null` | `null` | project-local | Per-machine git auto-sync toggle. `null` means "unanswered"; the editor's onboarding modal triggers on first remote-detected open. | -| `autoSync.default` | `boolean \| null` | `null` | project | Committed seed for each machine's first-open auto-sync choice: `true` = on, `false` = off, `null` = ask. Lets a maintainer pre-answer the onboarding prompt for everyone who clones; a per-machine `autoSync.enabled` overrides it. | +| `autoSync.mode` | `"off" \| "follow" \| "full" \| null` | `null` | project-local | Per-machine sync mode for this project: `off` (no sync), `follow` (one-directional — pull remote changes, never push your own; the earlier value `pull` is accepted as an alias), `full` (bidirectional pull and push). `null` means "unanswered"; the editor's onboarding modal triggers on first remote-detected open. Supersedes `autoSync.enabled`. | +| `autoSync.enabled` | `boolean \| null` | `null` | project-local | Legacy per-machine auto-sync toggle, superseded by `autoSync.mode`. Read only when `mode` is absent (`true` = full, `false` = off). `null` means "unanswered". | +| `autoSync.default` | `"off" \| "follow" \| "full" \| boolean \| null` | `null` | project | Committed seed for each machine's first-open sync mode: `off` / `follow` / `full`, or the legacy boolean (`true` = full, `false` = off). `null` = ask. Lets a maintainer pre-answer the onboarding prompt for everyone who clones; a per-machine `autoSync.mode` overrides it. Compatibility note: a committed string value (`off` / `follow` / `full`) is rejected by app versions released before `autoSync.mode` existed, resetting that machine to config defaults until it updates (it never silently syncs). The legacy boolean seed (`true` / `false`) stays readable by older apps — commit `follow` only once collaborators are on a current version. | | `editor.wordWrap` | boolean | `true` | user | Soft-wrap long lines in the source-mode CodeMirror editor. A personal preference, not project-shared. Toggle from the Settings pane. | | `appearance.preview.autoOpen` | boolean | `true` | user | Whether the agent should open or refresh the OpenKnowledge preview when it edits a doc through the MCP. Default `true` lets the agent route the preview by host capability: the host's in-app browser (Cursor preview pane, Codex's built-in browser, Claude Code Desktop) when one exists, the system browser otherwise. Set `false` to keep the agent's hands off your preview window. This is useful when you're already viewing the doc in OK Desktop, a browser tab on a second display, a non-default browser, or any flow where your extensions / accessibility tooling only work in your own browser. The agent then surfaces the URL on request but does not navigate. The change takes effect on the next preview-related tool call. Toggle from **Settings → Preferences → "Open preview when agent edits"**. | | `terminal.enabled` | `boolean \| null` | `null` | project-local | Opt-out for the in-app terminal (a real OS shell at full user privilege). On by default; set `false` to disable it for this project on this machine. | diff --git a/packages/app/src/components/AutoSyncEnableWarning.tsx b/packages/app/src/components/AutoSyncEnableWarning.tsx index 4e854cbdc..988e7f032 100644 --- a/packages/app/src/components/AutoSyncEnableWarning.tsx +++ b/packages/app/src/components/AutoSyncEnableWarning.tsx @@ -1,8 +1,37 @@ import { Trans } from '@lingui/react/macro'; -import { ArrowRightLeft, Eye, GitCommitVertical } from 'lucide-react'; +import { + ArrowDownToLine, + ArrowRightLeft, + Eye, + GitCommitVertical, + GitMerge, + Laptop, +} from 'lucide-react'; +import type { ReactNode } from 'react'; +import type { AutoSyncOnboardingVariant } from '@/components/auto-sync-onboarding-gate'; import { DialogDescription, DialogTitle } from '@/components/ui/dialog'; -export function AutoSyncEnableDialogIntro() { +interface SyncVariantProps { + /** 'full' = bidirectional sync copy (default); 'pull' = one-directional. */ + variant?: AutoSyncOnboardingVariant; +} + +export function AutoSyncEnableDialogIntro({ variant = 'full' }: SyncVariantProps) { + if (variant === 'follow') { + return ( + <> + + Enable Follow? + + + + Follow fetches updates from your remote git repository and fast-forwards your copy + automatically. Your local edits stay on this computer and are never pushed. + + + + ); + } return ( <> @@ -18,7 +47,7 @@ export function AutoSyncEnableDialogIntro() { ); } -export function AutoSyncEnableWarning() { +export function AutoSyncEnableWarning({ variant = 'full' }: SyncVariantProps) { return (

@@ -28,48 +57,104 @@ export function AutoSyncEnableWarning() { Heads up

-
+ {variant === 'follow' ? : } +
+
+ ); +} + +function FullSyncBullets() { + return ( + <> +
-
+ } + title={Uncommitted changes} + body={Pulls may overwrite uncommitted edits in your local files.} + /> +
-
-
+ } + title={Commits happen automatically} + body={ + + OpenKnowledge will create commits and push them to your remote automatically. If you do + not want automatic commits in your git history, you should not enable auto-sync. + + } + /> +